initial commit of C++ back end and react front end
This commit is contained in:
48
interface/src/containers/APConfiguration.js
Normal file
48
interface/src/containers/APConfiguration.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import Tabs, { Tab } from 'material-ui/Tabs';
|
||||
|
||||
import MenuAppBar from '../components/MenuAppBar';
|
||||
import APSettings from './APSettings';
|
||||
import APStatus from './APStatus';
|
||||
|
||||
class APConfiguration extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedTab: "apStatus",
|
||||
selectedNetwork: null
|
||||
};
|
||||
this.selectNetwork = this.selectNetwork.bind(this);
|
||||
this.deselectNetwork = this.deselectNetwork.bind(this);
|
||||
}
|
||||
|
||||
selectNetwork(network) {
|
||||
this.setState({ selectedTab: "wifiSettings", selectedNetwork:network });
|
||||
}
|
||||
|
||||
deselectNetwork(network) {
|
||||
this.setState({ selectedNetwork:null });
|
||||
}
|
||||
|
||||
handleTabChange = (event, selectedTab) => {
|
||||
this.setState({ selectedTab });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selectedTab } = this.state;
|
||||
return (
|
||||
<MenuAppBar sectionTitle="AP Configuration">
|
||||
<Tabs value={selectedTab} onChange={this.handleTabChange} indicatorColor="primary" textColor="primary" fullWidth centered scrollable>
|
||||
<Tab value="apStatus" label="AP Status" />
|
||||
<Tab value="apSettings" label="AP Settings" />
|
||||
</Tabs>
|
||||
{selectedTab === "apStatus" && <APStatus fullDetails={true} />}
|
||||
{selectedTab === "apSettings" && <APSettings />}
|
||||
</MenuAppBar>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default APConfiguration;
|
70
interface/src/containers/APSettings.js
Normal file
70
interface/src/containers/APSettings.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { AP_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
||||
import SectionContent from '../components/SectionContent';
|
||||
import SnackbarNotification from '../components/SnackbarNotification';
|
||||
import APSettingsForm from '../forms/APSettingsForm';
|
||||
import { simpleGet } from '../helpers/SimpleGet';
|
||||
import { simplePost } from '../helpers/SimplePost';
|
||||
|
||||
class APSettings extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
apSettings:null,
|
||||
apSettingsFetched: false,
|
||||
errorMessage:null
|
||||
};
|
||||
|
||||
this.setState = this.setState.bind(this);
|
||||
this.loadAPSettings = this.loadAPSettings.bind(this);
|
||||
this.saveAPSettings = this.saveAPSettings.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadAPSettings();
|
||||
}
|
||||
|
||||
loadAPSettings() {
|
||||
simpleGet(
|
||||
AP_SETTINGS_ENDPOINT,
|
||||
this.setState,
|
||||
this.raiseNotification,
|
||||
"apSettings",
|
||||
"apSettingsFetched"
|
||||
);
|
||||
}
|
||||
|
||||
saveAPSettings(e) {
|
||||
simplePost(
|
||||
AP_SETTINGS_ENDPOINT,
|
||||
this.state,
|
||||
this.setState,
|
||||
this.raiseNotification,
|
||||
"apSettings",
|
||||
"apSettingsFetched"
|
||||
);
|
||||
}
|
||||
|
||||
wifiSettingValueChange = name => event => {
|
||||
const { apSettings } = this.state;
|
||||
apSettings[name] = event.target.value;
|
||||
this.setState({apSettings});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { apSettingsFetched, apSettings, errorMessage } = this.state;
|
||||
return (
|
||||
<SectionContent title="AP Settings">
|
||||
<SnackbarNotification notificationRef={(raiseNotification)=>this.raiseNotification = raiseNotification} />
|
||||
<APSettingsForm apSettingsFetched={apSettingsFetched} apSettings={apSettings} errorMessage={errorMessage}
|
||||
onSubmit={this.saveAPSettings} onReset={this.loadAPSettings} handleValueChange={this.wifiSettingValueChange} />
|
||||
</SectionContent>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default APSettings;
|
165
interface/src/containers/APStatus.js
Normal file
165
interface/src/containers/APStatus.js
Normal file
@@ -0,0 +1,165 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { withStyles } from 'material-ui/styles';
|
||||
import Button from 'material-ui/Button';
|
||||
import { LinearProgress } from 'material-ui/Progress';
|
||||
import Typography from 'material-ui/Typography';
|
||||
import List, { ListItem, ListItemText } from 'material-ui/List';
|
||||
import Avatar from 'material-ui/Avatar';
|
||||
import Divider from 'material-ui/Divider';
|
||||
import SettingsInputAntennaIcon from 'material-ui-icons/SettingsInputAntenna';
|
||||
import DeviceHubIcon from 'material-ui-icons/DeviceHub';
|
||||
import ComputerIcon from 'material-ui-icons/Computer';
|
||||
|
||||
import SnackbarNotification from '../components/SnackbarNotification'
|
||||
import SectionContent from '../components/SectionContent'
|
||||
|
||||
import * as Highlight from '../constants/Highlight';
|
||||
import { AP_STATUS_ENDPOINT } from '../constants/Endpoints';
|
||||
import { simpleGet } from '../helpers/SimpleGet';
|
||||
|
||||
const styles = theme => ({
|
||||
["apStatus_" + Highlight.SUCCESS]: {
|
||||
backgroundColor: theme.palette.highlight_success
|
||||
},
|
||||
["apStatus_" + Highlight.IDLE]: {
|
||||
backgroundColor: theme.palette.highlight_idle
|
||||
},
|
||||
fetching: {
|
||||
margin: theme.spacing.unit * 4,
|
||||
textAlign: "center"
|
||||
},
|
||||
button: {
|
||||
marginRight: theme.spacing.unit * 2,
|
||||
marginTop: theme.spacing.unit * 2,
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
this.raiseNotification=this.raiseNotification.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadAPStatus();
|
||||
}
|
||||
|
||||
loadAPStatus() {
|
||||
simpleGet(
|
||||
AP_STATUS_ENDPOINT,
|
||||
this.setState,
|
||||
this.raiseNotification
|
||||
);
|
||||
}
|
||||
|
||||
raiseNotification(errorMessage) {
|
||||
this.snackbarNotification(errorMessage);
|
||||
}
|
||||
|
||||
apStatusHighlight(status){
|
||||
return status.active ? Highlight.SUCCESS : Highlight.IDLE;
|
||||
}
|
||||
|
||||
apStatus(status){
|
||||
return status.active ? "Active" : "Inactive";
|
||||
}
|
||||
|
||||
// active, ip_address, mac_address, station_num
|
||||
|
||||
renderAPStatus(status, fullDetails, classes){
|
||||
const listItems = [];
|
||||
|
||||
listItems.push(
|
||||
<ListItem key="ap_status">
|
||||
<Avatar className={classes["apStatus_" + this.apStatusHighlight(status)]}>
|
||||
<SettingsInputAntennaIcon />
|
||||
</Avatar>
|
||||
<ListItemText primary="Status" secondary={this.apStatus(status)} />
|
||||
</ListItem>
|
||||
);
|
||||
listItems.push(<Divider key="ap_status_divider" inset component="li" />);
|
||||
|
||||
listItems.push(
|
||||
<ListItem key="ip_address">
|
||||
<Avatar>IP</Avatar>
|
||||
<ListItemText primary="IP Address" secondary={status.ip_address} />
|
||||
</ListItem>
|
||||
);
|
||||
listItems.push(<Divider key="ip_address_divider" inset component="li" />);
|
||||
|
||||
listItems.push(
|
||||
<ListItem key="mac_address">
|
||||
<Avatar>
|
||||
<DeviceHubIcon />
|
||||
</Avatar>
|
||||
<ListItemText primary="MAC Address" secondary={status.mac_address} />
|
||||
</ListItem>
|
||||
);
|
||||
listItems.push(<Divider key="mac_address_divider" inset component="li" />);
|
||||
|
||||
listItems.push(
|
||||
<ListItem key="station_num">
|
||||
<Avatar>
|
||||
<ComputerIcon />
|
||||
</Avatar>
|
||||
<ListItemText primary="AP Clients" secondary={status.station_num} />
|
||||
</ListItem>
|
||||
);
|
||||
listItems.push(<Divider key="mac_address_divider" inset component="li" />);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<List>
|
||||
{listItems}
|
||||
</List>
|
||||
<Button variant="raised" color="secondary" className={classes.button} onClick={this.loadAPStatus}>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { status, fetched, errorMessage } = this.state;
|
||||
const { classes, fullDetails } = this.props;
|
||||
|
||||
return (
|
||||
<SectionContent title="AP Status">
|
||||
<SnackbarNotification notificationRef={(snackbarNotification)=>this.snackbarNotification = snackbarNotification} />
|
||||
{
|
||||
!fetched ?
|
||||
<div>
|
||||
<LinearProgress className={classes.fetching}/>
|
||||
<Typography variant="display1" className={classes.fetching}>
|
||||
Loading...
|
||||
</Typography>
|
||||
</div>
|
||||
:
|
||||
status ? this.renderAPStatus(status, fullDetails, classes)
|
||||
:
|
||||
<div>
|
||||
<Typography variant="display1" className={classes.fetching}>
|
||||
{errorMessage}
|
||||
</Typography>
|
||||
<Button variant="raised" color="secondary" className={classes.button} onClick={this.loadAPStatus}>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
</SectionContent>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(APStatus);
|
37
interface/src/containers/NTPConfiguration.js
Normal file
37
interface/src/containers/NTPConfiguration.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import React, { Component } from 'react';
|
||||
import MenuAppBar from '../components/MenuAppBar';
|
||||
import NTPSettings from './NTPSettings';
|
||||
import NTPStatus from './NTPStatus';
|
||||
|
||||
import Tabs, { Tab } from 'material-ui/Tabs';
|
||||
|
||||
class NTPConfiguration extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedTab: "ntpStatus"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
handleTabChange = (event, selectedTab) => {
|
||||
this.setState({ selectedTab });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selectedTab } = this.state;
|
||||
return (
|
||||
<MenuAppBar sectionTitle="NTP Configuration">
|
||||
<Tabs value={selectedTab} onChange={this.handleTabChange} indicatorColor="primary" textColor="primary" fullWidth centered scrollable>
|
||||
<Tab value="ntpStatus" label="NTP Status" />
|
||||
<Tab value="ntpSettings" label="NTP Settings" />
|
||||
</Tabs>
|
||||
{selectedTab === "ntpStatus" && <NTPStatus />}
|
||||
{selectedTab === "ntpSettings" && <NTPSettings />}
|
||||
</MenuAppBar>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default NTPConfiguration
|
70
interface/src/containers/NTPSettings.js
Normal file
70
interface/src/containers/NTPSettings.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { NTP_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
||||
import SectionContent from '../components/SectionContent';
|
||||
import SnackbarNotification from '../components/SnackbarNotification';
|
||||
import NTPSettingsForm from '../forms/NTPSettingsForm';
|
||||
import { simpleGet } from '../helpers/SimpleGet';
|
||||
import { simplePost } from '../helpers/SimplePost';
|
||||
|
||||
class NTPSettings extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
ntpSettings:{},
|
||||
ntpSettingsFetched: false,
|
||||
errorMessage:null
|
||||
};
|
||||
|
||||
this.setState = this.setState.bind(this);
|
||||
this.loadNTPSettings = this.loadNTPSettings.bind(this);
|
||||
this.saveNTPSettings = this.saveNTPSettings.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadNTPSettings();
|
||||
}
|
||||
|
||||
loadNTPSettings() {
|
||||
simpleGet(
|
||||
NTP_SETTINGS_ENDPOINT,
|
||||
this.setState,
|
||||
this.raiseNotification,
|
||||
"ntpSettings",
|
||||
"ntpSettingsFetched"
|
||||
);
|
||||
}
|
||||
|
||||
saveNTPSettings(e) {
|
||||
simplePost(
|
||||
NTP_SETTINGS_ENDPOINT,
|
||||
this.state,
|
||||
this.setState,
|
||||
this.raiseNotification,
|
||||
"ntpSettings",
|
||||
"ntpSettingsFetched"
|
||||
);
|
||||
}
|
||||
|
||||
ntpSettingValueChange = name => event => {
|
||||
const { ntpSettings } = this.state;
|
||||
ntpSettings[name] = event.target.value;
|
||||
this.setState({ntpSettings});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { ntpSettingsFetched, ntpSettings, errorMessage } = this.state;
|
||||
return (
|
||||
<SectionContent title="NTP Settings">
|
||||
<SnackbarNotification notificationRef={(raiseNotification)=>this.raiseNotification = raiseNotification} />
|
||||
<NTPSettingsForm ntpSettingsFetched={ntpSettingsFetched} ntpSettings={ntpSettings} errorMessage={errorMessage}
|
||||
onSubmit={this.saveNTPSettings} onReset={this.loadNTPSettings} handleValueChange={this.ntpSettingValueChange} />
|
||||
</SectionContent>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default NTPSettings;
|
189
interface/src/containers/NTPStatus.js
Normal file
189
interface/src/containers/NTPStatus.js
Normal file
@@ -0,0 +1,189 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { withStyles } from 'material-ui/styles';
|
||||
import Button from 'material-ui/Button';
|
||||
import { LinearProgress } from 'material-ui/Progress';
|
||||
import Typography from 'material-ui/Typography';
|
||||
import List, { ListItem, ListItemText } from 'material-ui/List';
|
||||
import Avatar from 'material-ui/Avatar';
|
||||
import Divider from 'material-ui/Divider';
|
||||
import SwapVerticalCircleIcon from 'material-ui-icons/SwapVerticalCircle';
|
||||
import AccessTimeIcon from 'material-ui-icons/AccessTime';
|
||||
import DNSIcon from 'material-ui-icons/Dns';
|
||||
import TimerIcon from 'material-ui-icons/Timer';
|
||||
import UpdateIcon from 'material-ui-icons/Update';
|
||||
import AvTimerIcon from 'material-ui-icons/AvTimer';
|
||||
|
||||
import { isSynchronized, ntpStatusHighlight, ntpStatus } from '../constants/NTPStatus';
|
||||
import * as Highlight from '../constants/Highlight';
|
||||
import { unixTimeToTimeAndDate } from '../constants/TimeFormat';
|
||||
import { NTP_STATUS_ENDPOINT } from '../constants/Endpoints';
|
||||
|
||||
import SnackbarNotification from '../components/SnackbarNotification';
|
||||
import SectionContent from '../components/SectionContent';
|
||||
|
||||
import { simpleGet } from '../helpers/SimpleGet';
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
const styles = theme => ({
|
||||
["ntpStatus_" + Highlight.SUCCESS]: {
|
||||
backgroundColor: theme.palette.highlight_success
|
||||
},
|
||||
["ntpStatus_" + Highlight.ERROR]: {
|
||||
backgroundColor: theme.palette.highlight_error
|
||||
},
|
||||
["ntpStatus_" + Highlight.WARN]: {
|
||||
backgroundColor: theme.palette.highlight_warn
|
||||
},
|
||||
fetching: {
|
||||
margin: theme.spacing.unit * 4,
|
||||
textAlign: "center"
|
||||
},
|
||||
button: {
|
||||
marginRight: theme.spacing.unit * 2,
|
||||
marginTop: theme.spacing.unit * 2,
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
this.raiseNotification=this.raiseNotification.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadNTPStatus();
|
||||
}
|
||||
|
||||
loadNTPStatus() {
|
||||
simpleGet(
|
||||
NTP_STATUS_ENDPOINT,
|
||||
this.setState,
|
||||
this.raiseNotification
|
||||
);
|
||||
}
|
||||
|
||||
raiseNotification(errorMessage) {
|
||||
this.snackbarNotification(errorMessage);
|
||||
}
|
||||
|
||||
renderNTPStatus(status, fullDetails, classes){
|
||||
const listItems = [];
|
||||
|
||||
listItems.push(
|
||||
<ListItem key="ntp_status">
|
||||
<Avatar className={classes["ntpStatus_" + ntpStatusHighlight(status)]}>
|
||||
<UpdateIcon />
|
||||
</Avatar>
|
||||
<ListItemText primary="Status" secondary={ntpStatus(status)} />
|
||||
</ListItem>
|
||||
);
|
||||
listItems.push(<Divider key="ntp_status_divider" inset component="li" />);
|
||||
|
||||
if (isSynchronized(status)) {
|
||||
listItems.push(
|
||||
<ListItem key="time_now">
|
||||
<Avatar>
|
||||
<AccessTimeIcon />
|
||||
</Avatar>
|
||||
<ListItemText primary="Time Now" secondary={unixTimeToTimeAndDate(status.now)} />
|
||||
</ListItem>
|
||||
);
|
||||
listItems.push(<Divider key="time_now_divider" inset component="li" />);
|
||||
}
|
||||
|
||||
listItems.push(
|
||||
<ListItem key="last_sync">
|
||||
<Avatar>
|
||||
<SwapVerticalCircleIcon />
|
||||
</Avatar>
|
||||
<ListItemText primary="Last Sync" secondary={status.last_sync > 0 ? unixTimeToTimeAndDate(status.last_sync) : "never" } />
|
||||
</ListItem>
|
||||
);
|
||||
listItems.push(<Divider key="last_sync_divider" inset component="li" />);
|
||||
|
||||
listItems.push(
|
||||
<ListItem key="ntp_server">
|
||||
<Avatar>
|
||||
<DNSIcon />
|
||||
</Avatar>
|
||||
<ListItemText primary="NTP Server" secondary={status.server} />
|
||||
</ListItem>
|
||||
);
|
||||
listItems.push(<Divider key="ntp_server_divider" inset component="li" />);
|
||||
|
||||
listItems.push(
|
||||
<ListItem key="sync_interval">
|
||||
<Avatar>
|
||||
<TimerIcon />
|
||||
</Avatar>
|
||||
<ListItemText primary="Sync Interval" secondary={moment.duration(status.interval, 'seconds').humanize()} />
|
||||
</ListItem>
|
||||
);
|
||||
listItems.push(<Divider key="sync_interval_divider" inset component="li" />);
|
||||
|
||||
listItems.push(
|
||||
<ListItem key="uptime">
|
||||
<Avatar>
|
||||
<AvTimerIcon />
|
||||
</Avatar>
|
||||
<ListItemText primary="Uptime" secondary={moment.duration(status.uptime, 'seconds').humanize()} />
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<List>
|
||||
{listItems}
|
||||
</List>
|
||||
<Button variant="raised" color="secondary" className={classes.button} onClick={this.loadNTPStatus}>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { status, fetched, errorMessage } = this.state;
|
||||
const { classes, fullDetails } = this.props;
|
||||
|
||||
return (
|
||||
<SectionContent title="NTP Status">
|
||||
<SnackbarNotification notificationRef={(snackbarNotification)=>this.snackbarNotification = snackbarNotification} />
|
||||
{
|
||||
!fetched ?
|
||||
<div>
|
||||
<LinearProgress className={classes.fetching}/>
|
||||
<Typography variant="display1" className={classes.fetching}>
|
||||
Loading...
|
||||
</Typography>
|
||||
</div>
|
||||
:
|
||||
status ? this.renderNTPStatus(status, fullDetails, classes)
|
||||
:
|
||||
<div>
|
||||
<Typography variant="display1" className={classes.fetching}>
|
||||
{errorMessage}
|
||||
</Typography>
|
||||
<Button variant="raised" color="secondary" className={classes.button} onClick={this.loadNTPStatus}>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
</SectionContent>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(NTPStatus);
|
15
interface/src/containers/OTAConfiguration.js
Normal file
15
interface/src/containers/OTAConfiguration.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import React, { Component } from 'react';
|
||||
import MenuAppBar from '../components/MenuAppBar';
|
||||
import OTASettings from './OTASettings';
|
||||
|
||||
class OTAConfiguration extends Component {
|
||||
render() {
|
||||
return (
|
||||
<MenuAppBar sectionTitle="OTA Configuration">
|
||||
<OTASettings />
|
||||
</MenuAppBar>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default OTAConfiguration
|
77
interface/src/containers/OTASettings.js
Normal file
77
interface/src/containers/OTASettings.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { OTA_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
||||
import SectionContent from '../components/SectionContent';
|
||||
import SnackbarNotification from '../components/SnackbarNotification';
|
||||
import OTASettingsForm from '../forms/OTASettingsForm';
|
||||
import { simpleGet } from '../helpers/SimpleGet';
|
||||
import { simplePost } from '../helpers/SimplePost';
|
||||
|
||||
class OTASettings extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
otaSettings:null,
|
||||
otaSettingsFetched: false,
|
||||
errorMessage:null
|
||||
};
|
||||
|
||||
this.setState = this.setState.bind(this);
|
||||
this.loadOTASettings = this.loadOTASettings.bind(this);
|
||||
this.saveOTASettings = this.saveOTASettings.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadOTASettings();
|
||||
}
|
||||
|
||||
loadOTASettings() {
|
||||
simpleGet(
|
||||
OTA_SETTINGS_ENDPOINT,
|
||||
this.setState,
|
||||
this.raiseNotification,
|
||||
"otaSettings",
|
||||
"otaSettingsFetched"
|
||||
);
|
||||
}
|
||||
|
||||
saveOTASettings(e) {
|
||||
simplePost(
|
||||
OTA_SETTINGS_ENDPOINT,
|
||||
this.state,
|
||||
this.setState,
|
||||
this.raiseNotification,
|
||||
"otaSettings",
|
||||
"otaSettingsFetched"
|
||||
);
|
||||
}
|
||||
|
||||
otaSettingValueChange = name => event => {
|
||||
const { otaSettings } = this.state;
|
||||
otaSettings[name] = event.target.value;
|
||||
this.setState({otaSettings});
|
||||
};
|
||||
|
||||
otaSettingCheckboxChange = name => event => {
|
||||
const { otaSettings } = this.state;
|
||||
otaSettings[name] = event.target.checked;
|
||||
this.setState({otaSettings});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { otaSettingsFetched, otaSettings, errorMessage } = this.state;
|
||||
return (
|
||||
<SectionContent title="OTA Settings">
|
||||
<SnackbarNotification notificationRef={(raiseNotification)=>this.raiseNotification = raiseNotification} />
|
||||
<OTASettingsForm otaSettingsFetched={otaSettingsFetched} otaSettings={otaSettings} errorMessage={errorMessage}
|
||||
onSubmit={this.saveOTASettings} onReset={this.loadOTASettings} handleValueChange={this.otaSettingValueChange}
|
||||
handleCheckboxChange={this.otaSettingCheckboxChange} />
|
||||
</SectionContent>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default OTASettings;
|
53
interface/src/containers/WiFiConfiguration.js
Normal file
53
interface/src/containers/WiFiConfiguration.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import Tabs, { Tab } from 'material-ui/Tabs';
|
||||
|
||||
import MenuAppBar from '../components/MenuAppBar';
|
||||
import WiFiNetworkScanner from './WiFiNetworkScanner';
|
||||
import WiFiSettings from './WiFiSettings';
|
||||
import WiFiStatus from './WiFiStatus';
|
||||
|
||||
class WiFiConfiguration extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedTab: "wifiStatus",
|
||||
selectedNetwork: null
|
||||
};
|
||||
this.selectNetwork = this.selectNetwork.bind(this);
|
||||
this.deselectNetwork = this.deselectNetwork.bind(this);
|
||||
}
|
||||
|
||||
// TODO - slightly inapproperate use of callback ref possibly.
|
||||
selectNetwork(network) {
|
||||
this.setState({ selectedTab: "wifiSettings", selectedNetwork:network });
|
||||
}
|
||||
|
||||
// deselects the network after the settings component mounts.
|
||||
deselectNetwork(network) {
|
||||
this.setState({ selectedNetwork:null });
|
||||
}
|
||||
|
||||
handleTabChange = (event, selectedTab) => {
|
||||
this.setState({ selectedTab });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selectedTab } = this.state;
|
||||
return (
|
||||
<MenuAppBar sectionTitle="WiFi Configuration">
|
||||
<Tabs value={selectedTab} onChange={this.handleTabChange} indicatorColor="primary" textColor="primary" fullWidth centered scrollable>
|
||||
<Tab value="wifiStatus" label="WiFi Status" />
|
||||
<Tab value="networkScanner" label="Network Scanner" />
|
||||
<Tab value="wifiSettings" label="WiFi Settings" />
|
||||
</Tabs>
|
||||
{selectedTab === "wifiStatus" && <WiFiStatus fullDetails={true} />}
|
||||
{selectedTab === "networkScanner" && <WiFiNetworkScanner selectNetwork={this.selectNetwork} />}
|
||||
{selectedTab === "wifiSettings" && <WiFiSettings deselectNetwork={this.deselectNetwork} selectedNetwork={this.state.selectedNetwork} />}
|
||||
</MenuAppBar>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default WiFiConfiguration;
|
118
interface/src/containers/WiFiNetworkScanner.js
Normal file
118
interface/src/containers/WiFiNetworkScanner.js
Normal file
@@ -0,0 +1,118 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { SCAN_NETWORKS_ENDPOINT, LIST_NETWORKS_ENDPOINT } from '../constants/Endpoints';
|
||||
import SectionContent from '../components/SectionContent';
|
||||
import WiFiNetworkSelector from '../forms/WiFiNetworkSelector';
|
||||
|
||||
const NUM_POLLS = 10
|
||||
const POLLING_FREQUENCY = 500
|
||||
const RETRY_EXCEPTION_TYPE = "retry"
|
||||
|
||||
class WiFiNetworkScanner extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.pollCount = 0;
|
||||
this.state = {
|
||||
scanningForNetworks: true,
|
||||
errorMessage:null,
|
||||
networkList: null
|
||||
};
|
||||
this.pollNetworkList = this.pollNetworkList.bind(this);
|
||||
this.requestNetworkScan = this.requestNetworkScan.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.scanNetworks();
|
||||
}
|
||||
|
||||
requestNetworkScan() {
|
||||
const { scanningForNetworks } = this.state;
|
||||
if (!scanningForNetworks) {
|
||||
this.scanNetworks();
|
||||
}
|
||||
}
|
||||
|
||||
scanNetworks() {
|
||||
this.pollCount = 0;
|
||||
this.setState({scanningForNetworks:true, networkList: null, errorMessage:null});
|
||||
fetch(SCAN_NETWORKS_ENDPOINT).then(response => {
|
||||
if (response.status === 202) {
|
||||
this.schedulePollTimeout();
|
||||
return;
|
||||
}
|
||||
throw Error("Scanning for networks returned unexpected response code: " + response.status);
|
||||
}).catch(error => {
|
||||
this.setState({scanningForNetworks:false, networkList: null, errorMessage:error.message});
|
||||
});
|
||||
}
|
||||
|
||||
schedulePollTimeout() {
|
||||
setTimeout(this.pollNetworkList, POLLING_FREQUENCY);
|
||||
}
|
||||
|
||||
retryError() {
|
||||
return {
|
||||
name:RETRY_EXCEPTION_TYPE,
|
||||
message:"Network list not ready, will retry in " + POLLING_FREQUENCY + "ms."
|
||||
};
|
||||
}
|
||||
|
||||
compareNetworks(network1,network2) {
|
||||
if (network1.rssi < network2.rssi)
|
||||
return 1;
|
||||
if (network1.rssi > network2.rssi)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
pollNetworkList() {
|
||||
fetch(LIST_NETWORKS_ENDPOINT)
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
return response.json();
|
||||
}
|
||||
if (response.status === 202) {
|
||||
if (++this.pollCount < NUM_POLLS){
|
||||
this.schedulePollTimeout();
|
||||
throw this.retryError();
|
||||
}else{
|
||||
throw Error("Device did not return network list in timley manner.");
|
||||
}
|
||||
}
|
||||
throw Error("Device returned unexpected response code: " + response.status);
|
||||
})
|
||||
.then(json => {
|
||||
json.networks.sort(this.compareNetworks)
|
||||
this.setState({scanningForNetworks:false, networkList: json, errorMessage:null})
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error.message);
|
||||
if (error.name !== RETRY_EXCEPTION_TYPE) {
|
||||
this.setState({scanningForNetworks:false, networkList: null, errorMessage:error.message});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { scanningForNetworks, networkList, errorMessage } = this.state;
|
||||
return (
|
||||
<SectionContent title="Network Scanner">
|
||||
<WiFiNetworkSelector scanningForNetworks={scanningForNetworks}
|
||||
networkList={networkList}
|
||||
errorMessage={errorMessage}
|
||||
requestNetworkScan={this.requestNetworkScan}
|
||||
selectNetwork={this.props.selectNetwork}
|
||||
/>
|
||||
</SectionContent>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
WiFiNetworkScanner.propTypes = {
|
||||
selectNetwork: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default (WiFiNetworkScanner);
|
102
interface/src/containers/WiFiSettings.js
Normal file
102
interface/src/containers/WiFiSettings.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { WIFI_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
||||
import SectionContent from '../components/SectionContent';
|
||||
import SnackbarNotification from '../components/SnackbarNotification';
|
||||
import WiFiSettingsForm from '../forms/WiFiSettingsForm';
|
||||
import { simpleGet } from '../helpers/SimpleGet';
|
||||
import { simplePost } from '../helpers/SimplePost';
|
||||
|
||||
class WiFiSettings extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
wifiSettingsFetched: false,
|
||||
wifiSettings:{},
|
||||
selectedNetwork: null,
|
||||
errorMessage:null
|
||||
};
|
||||
|
||||
this.setState = this.setState.bind(this);
|
||||
this.loadWiFiSettings = this.loadWiFiSettings.bind(this);
|
||||
this.saveWiFiSettings = this.saveWiFiSettings.bind(this);
|
||||
this.deselectNetwork = this.deselectNetwork.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { selectedNetwork, deselectNetwork } = this.props;
|
||||
if (selectedNetwork){
|
||||
var wifiSettings = {
|
||||
ssid:selectedNetwork.ssid,
|
||||
password:"",
|
||||
hostname:"esp8266-react",
|
||||
static_ip_config:false,
|
||||
}
|
||||
this.setState({wifiSettingsFetched:true, wifiSettings, selectedNetwork, errorMessage:null});
|
||||
deselectNetwork();
|
||||
}else {
|
||||
this.loadWiFiSettings();
|
||||
}
|
||||
}
|
||||
|
||||
loadWiFiSettings() {
|
||||
this.deselectNetwork();
|
||||
|
||||
simpleGet(
|
||||
WIFI_SETTINGS_ENDPOINT,
|
||||
this.setState,
|
||||
this.raiseNotification,
|
||||
"wifiSettings",
|
||||
"wifiSettingsFetched"
|
||||
);
|
||||
}
|
||||
|
||||
saveWiFiSettings(e) {
|
||||
simplePost(
|
||||
WIFI_SETTINGS_ENDPOINT,
|
||||
this.state,
|
||||
this.setState,
|
||||
this.raiseNotification,
|
||||
"wifiSettings",
|
||||
"wifiSettingsFetched"
|
||||
);
|
||||
}
|
||||
|
||||
deselectNetwork(nextNetwork) {
|
||||
this.setState({selectedNetwork:null});
|
||||
}
|
||||
|
||||
wifiSettingValueChange = name => event => {
|
||||
const { wifiSettings } = this.state;
|
||||
wifiSettings[name] = event.target.value;
|
||||
this.setState({wifiSettings});
|
||||
};
|
||||
|
||||
wifiSettingCheckboxChange = name => event => {
|
||||
const { wifiSettings } = this.state;
|
||||
wifiSettings[name] = event.target.checked;
|
||||
this.setState({wifiSettings});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { wifiSettingsFetched, wifiSettings, errorMessage, selectedNetwork } = this.state;
|
||||
return (
|
||||
<SectionContent title="WiFi Settings">
|
||||
<SnackbarNotification notificationRef={(raiseNotification)=>this.raiseNotification = raiseNotification} />
|
||||
<WiFiSettingsForm wifiSettingsFetched={wifiSettingsFetched} wifiSettings={wifiSettings} errorMessage={errorMessage} selectedNetwork={selectedNetwork} deselectNetwork={this.deselectNetwork}
|
||||
onSubmit={this.saveWiFiSettings} onReset={this.loadWiFiSettings} handleValueChange={this.wifiSettingValueChange} handleCheckboxChange={this.wifiSettingCheckboxChange} />
|
||||
</SectionContent>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
WiFiSettings.propTypes = {
|
||||
deselectNetwork: PropTypes.func,
|
||||
selectedNetwork: PropTypes.object
|
||||
};
|
||||
|
||||
export default WiFiSettings;
|
188
interface/src/containers/WiFiStatus.js
Normal file
188
interface/src/containers/WiFiStatus.js
Normal file
@@ -0,0 +1,188 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { withStyles } from 'material-ui/styles';
|
||||
import Button from 'material-ui/Button';
|
||||
import { LinearProgress } from 'material-ui/Progress';
|
||||
import Typography from 'material-ui/Typography';
|
||||
|
||||
import SnackbarNotification 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 List, { ListItem, ListItemText } from 'material-ui/List';
|
||||
import Avatar from 'material-ui/Avatar';
|
||||
import Divider from 'material-ui/Divider';
|
||||
import WifiIcon from 'material-ui-icons/Wifi';
|
||||
import DNSIcon from 'material-ui-icons/Dns';
|
||||
import SettingsInputComponentIcon from 'material-ui-icons/SettingsInputComponent';
|
||||
import SettingsInputAntennaIcon from 'material-ui-icons/SettingsInputAntenna';
|
||||
|
||||
const styles = theme => ({
|
||||
["wifiStatus_" + Highlight.IDLE]: {
|
||||
backgroundColor: theme.palette.highlight_idle
|
||||
},
|
||||
["wifiStatus_" + Highlight.SUCCESS]: {
|
||||
backgroundColor: theme.palette.highlight_success
|
||||
},
|
||||
["wifiStatus_" + Highlight.ERROR]: {
|
||||
backgroundColor: theme.palette.highlight_error
|
||||
},
|
||||
["wifiStatus_" + Highlight.WARN]: {
|
||||
backgroundColor: theme.palette.highlight_warn
|
||||
},
|
||||
fetching: {
|
||||
margin: theme.spacing.unit * 4,
|
||||
textAlign: "center"
|
||||
},
|
||||
button: {
|
||||
marginRight: theme.spacing.unit * 2,
|
||||
marginTop: theme.spacing.unit * 2,
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
dnsServers(status) {
|
||||
if (!status.dns_ip_1){
|
||||
return "none";
|
||||
}
|
||||
return status.dns_ip_1 + (status.dns_ip_2 ? ','+status.dns_ip_2 : '');
|
||||
}
|
||||
|
||||
loadWiFiStatus() {
|
||||
simpleGet(
|
||||
WIFI_STATUS_ENDPOINT,
|
||||
this.setState,
|
||||
this.raiseNotification
|
||||
);
|
||||
}
|
||||
|
||||
renderWiFiStatus(status, fullDetails, classes) {
|
||||
const listItems = [];
|
||||
|
||||
listItems.push(
|
||||
<ListItem key="connection_status">
|
||||
<Avatar className={classes["wifiStatus_" + connectionStatusHighlight(status)]}>
|
||||
<WifiIcon />
|
||||
</Avatar>
|
||||
<ListItemText primary="Connection Status" secondary={connectionStatus(status)} />
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
if (fullDetails && isConnected(status)) {
|
||||
listItems.push(<Divider key="connection_status_divider" inset component="li" />);
|
||||
|
||||
listItems.push(
|
||||
<ListItem key="ssid">
|
||||
<Avatar>
|
||||
<SettingsInputAntennaIcon />
|
||||
</Avatar>
|
||||
<ListItemText primary="SSID" secondary={status.ssid} />
|
||||
</ListItem>
|
||||
);
|
||||
listItems.push(<Divider key="ssid_divider" inset component="li" />);
|
||||
|
||||
listItems.push(
|
||||
<ListItem key="ip_address">
|
||||
<Avatar>IP</Avatar>
|
||||
<ListItemText primary="IP Address" secondary={status.local_ip} />
|
||||
</ListItem>
|
||||
);
|
||||
listItems.push(<Divider key="ip_address_divider" inset component="li" />);
|
||||
|
||||
listItems.push(
|
||||
<ListItem key="subnet_mask">
|
||||
<Avatar>#</Avatar>
|
||||
<ListItemText primary="Subnet Mask" secondary={status.subnet_mask} />
|
||||
</ListItem>
|
||||
);
|
||||
listItems.push(<Divider key="subnet_mask_divider" inset component="li" />);
|
||||
|
||||
listItems.push(
|
||||
<ListItem key="gateway_ip">
|
||||
<Avatar>
|
||||
<SettingsInputComponentIcon />
|
||||
</Avatar>
|
||||
<ListItemText primary="Gateway IP" secondary={status.gateway_ip ? status.gateway_ip : "none"} />
|
||||
</ListItem>
|
||||
);
|
||||
listItems.push(<Divider key="gateway_ip_divider" inset component="li" />);
|
||||
|
||||
listItems.push(
|
||||
<ListItem key="dns_server_ip">
|
||||
<Avatar>
|
||||
<DNSIcon />
|
||||
</Avatar>
|
||||
<ListItemText primary="DNS Server IP" secondary={this.dnsServers(status)} />
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<List>
|
||||
{listItems}
|
||||
</List>
|
||||
<Button variant="raised" color="secondary" className={classes.button} onClick={this.loadWiFiStatus}>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
const { status, fetched, errorMessage } = this.state;
|
||||
const { classes, fullDetails } = this.props;
|
||||
|
||||
return (
|
||||
<SectionContent title="WiFi Status">
|
||||
<SnackbarNotification notificationRef={(raiseNotification)=>this.raiseNotification = raiseNotification} />
|
||||
{
|
||||
!fetched ?
|
||||
<div>
|
||||
<LinearProgress className={classes.fetching}/>
|
||||
<Typography variant="display1" className={classes.fetching}>
|
||||
Loading...
|
||||
</Typography>
|
||||
</div>
|
||||
:
|
||||
status ? this.renderWiFiStatus(status, fullDetails, classes)
|
||||
:
|
||||
<div>
|
||||
<Typography variant="display1" className={classes.fetching}>
|
||||
{errorMessage}
|
||||
</Typography>
|
||||
<Button variant="raised" color="secondary" className={classes.button} onClick={this.loadWiFiStatus}>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
</SectionContent>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(WiFiStatus);
|
Reference in New Issue
Block a user