initial commit of C++ back end and react front end
This commit is contained in:
		
							
								
								
									
										54
									
								
								interface/src/App.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								interface/src/App.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
import React, { Component } from 'react';
 | 
			
		||||
 | 
			
		||||
import AppRouting from './AppRouting';
 | 
			
		||||
 | 
			
		||||
import JssProvider from 'react-jss/lib/JssProvider';
 | 
			
		||||
import { create } from 'jss';
 | 
			
		||||
 | 
			
		||||
import Reboot from 'material-ui/Reboot';
 | 
			
		||||
 | 
			
		||||
import blueGrey from 'material-ui/colors/blueGrey';
 | 
			
		||||
import indigo from 'material-ui/colors/indigo';
 | 
			
		||||
import orange from 'material-ui/colors/orange';
 | 
			
		||||
import red from 'material-ui/colors/red';
 | 
			
		||||
import green from 'material-ui/colors/green';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  MuiThemeProvider,
 | 
			
		||||
  createMuiTheme,
 | 
			
		||||
  createGenerateClassName,
 | 
			
		||||
  jssPreset,
 | 
			
		||||
} from 'material-ui/styles';
 | 
			
		||||
 | 
			
		||||
// Our theme
 | 
			
		||||
const theme = createMuiTheme({
 | 
			
		||||
  palette: {
 | 
			
		||||
    primary: indigo,
 | 
			
		||||
    secondary: blueGrey,
 | 
			
		||||
    highlight_idle: blueGrey[900],
 | 
			
		||||
    highlight_warn: orange[500],
 | 
			
		||||
    highlight_error: red[500],
 | 
			
		||||
    highlight_success: green[500],
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// JSS instance
 | 
			
		||||
const jss = create(jssPreset());
 | 
			
		||||
 | 
			
		||||
// Class name generator.
 | 
			
		||||
const generateClassName = createGenerateClassName();
 | 
			
		||||
 | 
			
		||||
class App extends Component {
 | 
			
		||||
	render() {
 | 
			
		||||
	   return (
 | 
			
		||||
		 <JssProvider jss={jss} generateClassName={generateClassName}>
 | 
			
		||||
			<MuiThemeProvider theme={theme}>
 | 
			
		||||
				<Reboot />
 | 
			
		||||
        <AppRouting />
 | 
			
		||||
			</MuiThemeProvider>
 | 
			
		||||
		 </JssProvider>
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default App
 | 
			
		||||
							
								
								
									
										25
									
								
								interface/src/AppRouting.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								interface/src/AppRouting.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
import React, { Component } from 'react';
 | 
			
		||||
 | 
			
		||||
import { Route, Redirect, Switch } from 'react-router';
 | 
			
		||||
 | 
			
		||||
// containers
 | 
			
		||||
import WiFiConfiguration from './containers/WiFiConfiguration';
 | 
			
		||||
import NTPConfiguration from './containers/NTPConfiguration';
 | 
			
		||||
import OTAConfiguration from './containers/OTAConfiguration';
 | 
			
		||||
import APConfiguration from './containers/APConfiguration';
 | 
			
		||||
 | 
			
		||||
class AppRouting extends Component {
 | 
			
		||||
	render() {
 | 
			
		||||
	   return (
 | 
			
		||||
       <Switch>
 | 
			
		||||
         <Route exact path="/wifi-configuration" component={WiFiConfiguration} />
 | 
			
		||||
				 <Route exact path="/ap-configuration" component={APConfiguration} />
 | 
			
		||||
				 <Route exact path="/ntp-configuration" component={NTPConfiguration} />
 | 
			
		||||
				 <Route exact path="/ota-configuration" component={OTAConfiguration} />
 | 
			
		||||
         <Redirect to="/wifi-configuration" />
 | 
			
		||||
       </Switch>
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default AppRouting;
 | 
			
		||||
							
								
								
									
										189
									
								
								interface/src/components/MenuAppBar.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								interface/src/components/MenuAppBar.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,189 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import { withStyles } from 'material-ui/styles';
 | 
			
		||||
import Drawer from 'material-ui/Drawer';
 | 
			
		||||
import AppBar from 'material-ui/AppBar';
 | 
			
		||||
import Toolbar from 'material-ui/Toolbar';
 | 
			
		||||
import Typography from 'material-ui/Typography';
 | 
			
		||||
import IconButton from 'material-ui/IconButton';
 | 
			
		||||
import Hidden from 'material-ui/Hidden';
 | 
			
		||||
import Divider from 'material-ui/Divider';
 | 
			
		||||
import {  Link } from 'react-router-dom';
 | 
			
		||||
import List, { ListItem, ListItemIcon, ListItemText } from 'material-ui/List';
 | 
			
		||||
 | 
			
		||||
import MenuIcon from 'material-ui-icons/Menu';
 | 
			
		||||
import WifiIcon from 'material-ui-icons/Wifi';
 | 
			
		||||
import SystemUpdateIcon from  'material-ui-icons/SystemUpdate';
 | 
			
		||||
import AccessTimeIcon from 'material-ui-icons/AccessTime';
 | 
			
		||||
import SettingsInputAntennaIcon from 'material-ui-icons/SettingsInputAntenna';
 | 
			
		||||
 | 
			
		||||
const drawerWidth = 250;
 | 
			
		||||
 | 
			
		||||
const styles = theme => ({
 | 
			
		||||
  root: {
 | 
			
		||||
    zIndex: 1,
 | 
			
		||||
    width: '100%',
 | 
			
		||||
    height: '100%',
 | 
			
		||||
  },
 | 
			
		||||
  toolbar: {
 | 
			
		||||
    paddingLeft: theme.spacing.unit,
 | 
			
		||||
    paddingRight:  theme.spacing.unit,
 | 
			
		||||
    [theme.breakpoints.up('md')]: {
 | 
			
		||||
      paddingLeft: theme.spacing.unit * 3,
 | 
			
		||||
      paddingRight: theme.spacing.unit  * 3,
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  appFrame: {
 | 
			
		||||
    position: 'relative',
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
    width: '100%',
 | 
			
		||||
    height: '100%',
 | 
			
		||||
  },
 | 
			
		||||
  appBar: {
 | 
			
		||||
    position: 'absolute',
 | 
			
		||||
    marginLeft: drawerWidth,
 | 
			
		||||
    [theme.breakpoints.up('md')]: {
 | 
			
		||||
      width: `calc(100% - ${drawerWidth}px)`,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  navIconHide: {
 | 
			
		||||
    [theme.breakpoints.up('md')]: {
 | 
			
		||||
      display: 'none',
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  drawerPaper: {
 | 
			
		||||
    width: drawerWidth,
 | 
			
		||||
    height: '100%',
 | 
			
		||||
    [theme.breakpoints.up('md')]: {
 | 
			
		||||
      width: drawerWidth,
 | 
			
		||||
      position:'fixed',
 | 
			
		||||
      left:0,
 | 
			
		||||
      top:0,
 | 
			
		||||
      overflow:'auto'
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  content: {
 | 
			
		||||
    backgroundColor: theme.palette.background.default,
 | 
			
		||||
    width:"100%",
 | 
			
		||||
    marginTop: 56,
 | 
			
		||||
    [theme.breakpoints.up('md')]: {
 | 
			
		||||
      paddingLeft: drawerWidth
 | 
			
		||||
    },
 | 
			
		||||
    [theme.breakpoints.up('sm')]: {
 | 
			
		||||
      height: 'calc(100% - 64px)',
 | 
			
		||||
      marginTop: 64,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
class MenuAppBar extends React.Component {
 | 
			
		||||
  state = {
 | 
			
		||||
    mobileOpen: false,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  handleDrawerToggle = () => {
 | 
			
		||||
    this.setState({ mobileOpen: !this.state.mobileOpen });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const { classes, theme, children, sectionTitle } = this.props;
 | 
			
		||||
 | 
			
		||||
    const drawer = (
 | 
			
		||||
      <div>
 | 
			
		||||
        <Toolbar>
 | 
			
		||||
            <Typography variant="title" color="primary">
 | 
			
		||||
              ESP8266 React
 | 
			
		||||
            </Typography>
 | 
			
		||||
          <Divider absolute />
 | 
			
		||||
        </Toolbar>
 | 
			
		||||
        <Divider />
 | 
			
		||||
        <List>
 | 
			
		||||
          <ListItem button component={Link} to='/wifi-configuration'>
 | 
			
		||||
            <ListItemIcon>
 | 
			
		||||
              <WifiIcon />
 | 
			
		||||
            </ListItemIcon>
 | 
			
		||||
            <ListItemText primary="WiFi Configuration" />
 | 
			
		||||
          </ListItem>
 | 
			
		||||
          <ListItem button component={Link} to='/ap-configuration'>
 | 
			
		||||
            <ListItemIcon>
 | 
			
		||||
              <SettingsInputAntennaIcon />
 | 
			
		||||
            </ListItemIcon>
 | 
			
		||||
            <ListItemText primary="AP Configuration" />
 | 
			
		||||
          </ListItem>
 | 
			
		||||
          <ListItem button component={Link} to='/ntp-configuration'>
 | 
			
		||||
            <ListItemIcon>
 | 
			
		||||
              <AccessTimeIcon />
 | 
			
		||||
            </ListItemIcon>
 | 
			
		||||
            <ListItemText primary="NTP Configuration" />
 | 
			
		||||
          </ListItem>
 | 
			
		||||
          <ListItem button component={Link} to='/ota-configuration'>
 | 
			
		||||
            <ListItemIcon>
 | 
			
		||||
              <SystemUpdateIcon />
 | 
			
		||||
            </ListItemIcon>
 | 
			
		||||
            <ListItemText primary="OTA Configuration" />
 | 
			
		||||
          </ListItem>
 | 
			
		||||
        </List>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className={classes.root}>
 | 
			
		||||
        <div className={classes.appFrame}>
 | 
			
		||||
          <AppBar className={classes.appBar}>
 | 
			
		||||
            <Toolbar className={classes.toolbar} disableGutters={true}>
 | 
			
		||||
              <IconButton
 | 
			
		||||
                color="inherit"
 | 
			
		||||
                aria-label="open drawer"
 | 
			
		||||
                onClick={this.handleDrawerToggle}
 | 
			
		||||
                className={classes.navIconHide}
 | 
			
		||||
              >
 | 
			
		||||
                <MenuIcon />
 | 
			
		||||
              </IconButton>
 | 
			
		||||
              <Typography variant="title" color="inherit" noWrap>
 | 
			
		||||
                {sectionTitle}
 | 
			
		||||
              </Typography>
 | 
			
		||||
            </Toolbar>
 | 
			
		||||
          </AppBar>
 | 
			
		||||
          <Hidden mdUp>
 | 
			
		||||
            <Drawer
 | 
			
		||||
              variant="temporary"
 | 
			
		||||
              anchor={theme.direction === 'rtl' ? 'right' : 'left'}
 | 
			
		||||
              open={this.state.mobileOpen}
 | 
			
		||||
              classes={{
 | 
			
		||||
                paper: classes.drawerPaper,
 | 
			
		||||
              }}
 | 
			
		||||
              onClose={this.handleDrawerToggle}
 | 
			
		||||
              ModalProps={{
 | 
			
		||||
                keepMounted: true, // Better open performance on mobile.
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              {drawer}
 | 
			
		||||
            </Drawer>
 | 
			
		||||
          </Hidden>
 | 
			
		||||
          <Hidden smDown implementation="css">
 | 
			
		||||
            <Drawer
 | 
			
		||||
              variant="permanent"
 | 
			
		||||
              open
 | 
			
		||||
              classes={{
 | 
			
		||||
                paper: classes.drawerPaper,
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              {drawer}
 | 
			
		||||
            </Drawer>
 | 
			
		||||
          </Hidden>
 | 
			
		||||
          <main className={classes.content}>
 | 
			
		||||
            {children}
 | 
			
		||||
          </main>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MenuAppBar.propTypes = {
 | 
			
		||||
  classes: PropTypes.object.isRequired,
 | 
			
		||||
  theme: PropTypes.object.isRequired,
 | 
			
		||||
  sectionTitle: PropTypes.string.isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default withStyles(styles, { withTheme: true })(MenuAppBar);
 | 
			
		||||
							
								
								
									
										36
									
								
								interface/src/components/SectionContent.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								interface/src/components/SectionContent.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
 | 
			
		||||
import Paper from 'material-ui/Paper';
 | 
			
		||||
import { withStyles } from 'material-ui/styles';
 | 
			
		||||
import Typography from 'material-ui/Typography';
 | 
			
		||||
 | 
			
		||||
const styles = theme => ({
 | 
			
		||||
  content: {
 | 
			
		||||
    padding: theme.spacing.unit * 2,
 | 
			
		||||
    margin: theme.spacing.unit * 2,
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function SectionContent(props) {
 | 
			
		||||
  const { children, classes, title } = props;
 | 
			
		||||
  return (
 | 
			
		||||
      <Paper className={classes.content}>
 | 
			
		||||
        <Typography variant="display1">
 | 
			
		||||
          {title}
 | 
			
		||||
        </Typography>
 | 
			
		||||
        {children}
 | 
			
		||||
      </Paper>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SectionContent.propTypes = {
 | 
			
		||||
  classes: PropTypes.object.isRequired,
 | 
			
		||||
	children: PropTypes.oneOfType([
 | 
			
		||||
        PropTypes.arrayOf(PropTypes.node),
 | 
			
		||||
        PropTypes.node
 | 
			
		||||
    ]).isRequired,
 | 
			
		||||
  title: PropTypes.string.isRequired
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default withStyles(styles)(SectionContent);
 | 
			
		||||
							
								
								
									
										74
									
								
								interface/src/components/SnackbarNotification.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								interface/src/components/SnackbarNotification.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import { withStyles } from 'material-ui/styles';
 | 
			
		||||
import Snackbar from 'material-ui/Snackbar';
 | 
			
		||||
import IconButton from 'material-ui/IconButton';
 | 
			
		||||
import CloseIcon from 'material-ui-icons/Close';
 | 
			
		||||
 | 
			
		||||
const styles = theme => ({
 | 
			
		||||
  close: {
 | 
			
		||||
    width: theme.spacing.unit * 4,
 | 
			
		||||
    height: theme.spacing.unit * 4,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
class SnackbarNotification extends React.Component {
 | 
			
		||||
  state = {
 | 
			
		||||
    open: false,
 | 
			
		||||
    message: null
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  raiseNotification = (message) => {
 | 
			
		||||
    this.setState({ open: true, message:message });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  handleClose = (event, reason) => {
 | 
			
		||||
    if (reason === 'clickaway') {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this.setState({ open: false });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  componentWillReceiveProps(nextProps){
 | 
			
		||||
    if (nextProps.notificationRef){
 | 
			
		||||
      nextProps.notificationRef(this.raiseNotification);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const { classes } = this.props;
 | 
			
		||||
    return (
 | 
			
		||||
        <Snackbar
 | 
			
		||||
          anchorOrigin={{
 | 
			
		||||
            vertical: 'bottom',
 | 
			
		||||
            horizontal: 'left',
 | 
			
		||||
          }}
 | 
			
		||||
          open={this.state.open}
 | 
			
		||||
          autoHideDuration={6000}
 | 
			
		||||
          onClose={this.handleClose}
 | 
			
		||||
          SnackbarContentProps={{
 | 
			
		||||
            'aria-describedby': 'message-id',
 | 
			
		||||
          }}
 | 
			
		||||
          message={<span id="message-id">{this.state.message}</span>}
 | 
			
		||||
          action={
 | 
			
		||||
            <IconButton
 | 
			
		||||
              key="close"
 | 
			
		||||
              aria-label="Close"
 | 
			
		||||
              color="inherit"
 | 
			
		||||
              className={classes.close}
 | 
			
		||||
              onClick={this.handleClose}
 | 
			
		||||
            >
 | 
			
		||||
              <CloseIcon />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
          }
 | 
			
		||||
        />
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SnackbarNotification.propTypes = {
 | 
			
		||||
  classes: PropTypes.object.isRequired,
 | 
			
		||||
  notificationRef: PropTypes.func.isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default withStyles(styles)(SnackbarNotification);
 | 
			
		||||
							
								
								
									
										28
									
								
								interface/src/constants/Endpoints.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								interface/src/constants/Endpoints.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
export const ENDPOINT_ROOT = "";
 | 
			
		||||
 | 
			
		||||
export const NTP_STATUS_PATH = "/ntpStatus";
 | 
			
		||||
export const NTP_STATUS_ENDPOINT = ENDPOINT_ROOT + NTP_STATUS_PATH;
 | 
			
		||||
 | 
			
		||||
export const NTP_SETTINGS_PATH = "/ntpSettings";
 | 
			
		||||
export const NTP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + NTP_SETTINGS_PATH;
 | 
			
		||||
 | 
			
		||||
export const AP_SETTINGS_PATH = "/apSettings";
 | 
			
		||||
export const AP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + AP_SETTINGS_PATH;
 | 
			
		||||
 | 
			
		||||
export const AP_STATUS_PATH = "/apStatus";
 | 
			
		||||
export const AP_STATUS_ENDPOINT = ENDPOINT_ROOT + AP_STATUS_PATH;
 | 
			
		||||
 | 
			
		||||
export const SCAN_NETWORKS_PATH = "/scanNetworks";
 | 
			
		||||
export const SCAN_NETWORKS_ENDPOINT = ENDPOINT_ROOT + SCAN_NETWORKS_PATH;
 | 
			
		||||
 | 
			
		||||
export const LIST_NETWORKS_PATH = "/listNetworks";
 | 
			
		||||
export const LIST_NETWORKS_ENDPOINT = ENDPOINT_ROOT + LIST_NETWORKS_PATH;
 | 
			
		||||
 | 
			
		||||
export const WIFI_SETTINGS_PATH = "/wifiSettings";
 | 
			
		||||
export const WIFI_SETTINGS_ENDPOINT = ENDPOINT_ROOT + WIFI_SETTINGS_PATH;
 | 
			
		||||
 | 
			
		||||
export const WIFI_STATUS_PATH = "/wifiStatus";
 | 
			
		||||
export const WIFI_STATUS_ENDPOINT = ENDPOINT_ROOT + WIFI_STATUS_PATH;
 | 
			
		||||
 | 
			
		||||
export const OTA_SETTINGS_PATH = "/otaSettings";
 | 
			
		||||
export const OTA_SETTINGS_ENDPOINT = ENDPOINT_ROOT + OTA_SETTINGS_PATH;
 | 
			
		||||
							
								
								
									
										4
									
								
								interface/src/constants/Highlight.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								interface/src/constants/Highlight.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
export const IDLE = "idle";
 | 
			
		||||
export const SUCCESS = "success";
 | 
			
		||||
export const ERROR = "error";
 | 
			
		||||
export const WARN = "warn";
 | 
			
		||||
							
								
								
									
										32
									
								
								interface/src/constants/NTPStatus.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								interface/src/constants/NTPStatus.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
import * as Highlight from '../constants/Highlight';
 | 
			
		||||
 | 
			
		||||
export const NTP_TIME_NOT_SET = 0;
 | 
			
		||||
export const NTP_TIME_NEEDS_SYNC = 1;
 | 
			
		||||
export const NTP_TIME_SET = 2;
 | 
			
		||||
 | 
			
		||||
export const isSynchronized = ntpStatus => ntpStatus && (ntpStatus.status === NTP_TIME_NEEDS_SYNC || ntpStatus.status === NTP_TIME_SET);
 | 
			
		||||
 | 
			
		||||
export const ntpStatusHighlight = ntpStatus => {
 | 
			
		||||
  switch (ntpStatus.status){
 | 
			
		||||
    case NTP_TIME_SET:
 | 
			
		||||
      return Highlight.SUCCESS;
 | 
			
		||||
    case NTP_TIME_NEEDS_SYNC:
 | 
			
		||||
      return Highlight.WARN;
 | 
			
		||||
    case NTP_TIME_NOT_SET:
 | 
			
		||||
    default:
 | 
			
		||||
      return Highlight.ERROR;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ntpStatus = ntpStatus => {
 | 
			
		||||
  switch (ntpStatus.status){
 | 
			
		||||
    case NTP_TIME_SET:
 | 
			
		||||
      return "Synchronized";
 | 
			
		||||
    case NTP_TIME_NEEDS_SYNC:
 | 
			
		||||
      return "Synchronization required";
 | 
			
		||||
    case NTP_TIME_NOT_SET:
 | 
			
		||||
      return "Time not set"
 | 
			
		||||
    default:
 | 
			
		||||
      return "Unknown";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								interface/src/constants/TimeFormat.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								interface/src/constants/TimeFormat.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
import moment from 'moment';
 | 
			
		||||
 | 
			
		||||
export const TIME_AND_DATE = 'DD/MM/YYYY HH:mm:ss';
 | 
			
		||||
export const unixTimeToTimeAndDate = unixTime => moment.unix(unixTime).format(TIME_AND_DATE);
 | 
			
		||||
							
								
								
									
										5
									
								
								interface/src/constants/WiFiAPModes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								interface/src/constants/WiFiAPModes.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
export const WIFI_AP_MODE_ALWAYS = 0;
 | 
			
		||||
export const WIFI_AP_MODE_DISCONNECTED = 1;
 | 
			
		||||
export const WIFI_AP_NEVER = 2;
 | 
			
		||||
 | 
			
		||||
export const isAPEnabled = apMode => apMode === WIFI_AP_MODE_ALWAYS || apMode === WIFI_AP_MODE_DISCONNECTED;
 | 
			
		||||
							
								
								
									
										44
									
								
								interface/src/constants/WiFiConnectionStatus.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								interface/src/constants/WiFiConnectionStatus.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
import * as Highlight from '../constants/Highlight';
 | 
			
		||||
 | 
			
		||||
export const WIFI_STATUS_IDLE = 0;
 | 
			
		||||
export const WIFI_STATUS_NO_SSID_AVAIL = 1;
 | 
			
		||||
export const WIFI_STATUS_CONNECTED = 3;
 | 
			
		||||
export const WIFI_STATUS_CONNECT_FAILED = 4;
 | 
			
		||||
export const WIFI_STATUS_CONNECTION_LOST = 5;
 | 
			
		||||
export const WIFI_STATUS_DISCONNECTED = 6;
 | 
			
		||||
 | 
			
		||||
export const isConnected = wifiStatus => wifiStatus && wifiStatus.status === WIFI_STATUS_CONNECTED;
 | 
			
		||||
 | 
			
		||||
export const connectionStatusHighlight = wifiStatus => {
 | 
			
		||||
  switch (wifiStatus.status){
 | 
			
		||||
    case WIFI_STATUS_IDLE:
 | 
			
		||||
    case WIFI_STATUS_DISCONNECTED:
 | 
			
		||||
      return Highlight.IDLE;
 | 
			
		||||
    case WIFI_STATUS_CONNECTED:
 | 
			
		||||
      return Highlight.SUCCESS;
 | 
			
		||||
    case WIFI_STATUS_CONNECT_FAILED:
 | 
			
		||||
    case WIFI_STATUS_CONNECTION_LOST:
 | 
			
		||||
      return Highlight.ERROR;
 | 
			
		||||
    default:
 | 
			
		||||
      return Highlight.WARN;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const connectionStatus = wifiStatus => {
 | 
			
		||||
  switch (wifiStatus.status){
 | 
			
		||||
    case WIFI_STATUS_IDLE:
 | 
			
		||||
      return "Idle";
 | 
			
		||||
    case WIFI_STATUS_NO_SSID_AVAIL:
 | 
			
		||||
      return "No SSID Available";
 | 
			
		||||
    case WIFI_STATUS_CONNECTED:
 | 
			
		||||
      return "Connected";
 | 
			
		||||
    case WIFI_STATUS_CONNECT_FAILED:
 | 
			
		||||
      return "Connection Failed";
 | 
			
		||||
    case WIFI_STATUS_CONNECTION_LOST:
 | 
			
		||||
      return "Connection Lost";
 | 
			
		||||
    case WIFI_STATUS_DISCONNECTED:
 | 
			
		||||
      return "Disconnected";
 | 
			
		||||
    default:
 | 
			
		||||
      return "Unknown";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								interface/src/constants/WiFiSecurityModes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								interface/src/constants/WiFiSecurityModes.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
export const WIFI_SECURITY_WPA_TKIP = 2;
 | 
			
		||||
export const WIFI_SECURITY_WEP = 5;
 | 
			
		||||
export const WIFI_SECURITY_WPA_CCMP = 4;
 | 
			
		||||
export const WIFI_SECURITY_NONE = 7;
 | 
			
		||||
export const WIFI_SECURITY_AUTO = 8;
 | 
			
		||||
 | 
			
		||||
export const isNetworkOpen = selectedNetwork => selectedNetwork && selectedNetwork.encryption_type === WIFI_SECURITY_NONE;
 | 
			
		||||
 | 
			
		||||
export const networkSecurityMode = selectedNetwork => {
 | 
			
		||||
  switch (selectedNetwork.encryption_type){
 | 
			
		||||
    case WIFI_SECURITY_WPA_TKIP:
 | 
			
		||||
    case WIFI_SECURITY_WPA_CCMP:
 | 
			
		||||
      return "WPA";
 | 
			
		||||
    case WIFI_SECURITY_WEP:
 | 
			
		||||
      return "WEP";
 | 
			
		||||
    case WIFI_SECURITY_AUTO:
 | 
			
		||||
      return "Auto";
 | 
			
		||||
    case WIFI_SECURITY_NONE:
 | 
			
		||||
      return "None";
 | 
			
		||||
    default:
 | 
			
		||||
      return "Unknown";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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);
 | 
			
		||||
							
								
								
									
										124
									
								
								interface/src/forms/APSettingsForm.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								interface/src/forms/APSettingsForm.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,124 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
 | 
			
		||||
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 { MenuItem } from 'material-ui/Menu';
 | 
			
		||||
 | 
			
		||||
import { TextValidator, ValidatorForm, SelectValidator } from 'react-material-ui-form-validator';
 | 
			
		||||
 | 
			
		||||
import {isAPEnabled} from '../constants/WiFiAPModes';
 | 
			
		||||
 | 
			
		||||
const styles = theme => ({
 | 
			
		||||
  loadingSettings: {
 | 
			
		||||
    margin: theme.spacing.unit,
 | 
			
		||||
  },
 | 
			
		||||
  loadingSettingsDetails: {
 | 
			
		||||
    margin: theme.spacing.unit * 4,
 | 
			
		||||
    textAlign: "center"
 | 
			
		||||
  },
 | 
			
		||||
  textField: {
 | 
			
		||||
    width: "100%"
 | 
			
		||||
  },
 | 
			
		||||
  selectField:{
 | 
			
		||||
    width: "100%",
 | 
			
		||||
    marginTop: theme.spacing.unit * 2,
 | 
			
		||||
    marginBottom: theme.spacing.unit
 | 
			
		||||
  },
 | 
			
		||||
  button: {
 | 
			
		||||
    marginRight: theme.spacing.unit * 2,
 | 
			
		||||
    marginTop: theme.spacing.unit * 2,
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
class APSettingsForm extends React.Component {
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const { classes, apSettingsFetched, apSettings, errorMessage, handleValueChange, onSubmit, onReset } = this.props;
 | 
			
		||||
    return (
 | 
			
		||||
      <div>
 | 
			
		||||
        {
 | 
			
		||||
         !apSettingsFetched ?
 | 
			
		||||
 | 
			
		||||
         <div className={classes.loadingSettings}>
 | 
			
		||||
           <LinearProgress className={classes.loadingSettingsDetails}/>
 | 
			
		||||
           <Typography variant="display1" className={classes.loadingSettingsDetails}>
 | 
			
		||||
             Loading...
 | 
			
		||||
           </Typography>
 | 
			
		||||
         </div>
 | 
			
		||||
 | 
			
		||||
         : apSettings ?
 | 
			
		||||
 | 
			
		||||
        <ValidatorForm onSubmit={onSubmit} ref="APSettingsForm">
 | 
			
		||||
 | 
			
		||||
          <SelectValidator name="provision_mode" label="Provide Access Point..." value={apSettings.provision_mode}  className={classes.selectField}
 | 
			
		||||
           onChange={handleValueChange('provision_mode')}>
 | 
			
		||||
            <MenuItem value={0}>Always</MenuItem>
 | 
			
		||||
            <MenuItem value={1}>When WiFi Disconnected</MenuItem>
 | 
			
		||||
            <MenuItem value={2}>Never</MenuItem>
 | 
			
		||||
          </SelectValidator>
 | 
			
		||||
 | 
			
		||||
          {
 | 
			
		||||
            isAPEnabled(apSettings.provision_mode) &&
 | 
			
		||||
            [
 | 
			
		||||
              <TextValidator key="ssid"
 | 
			
		||||
                validators={['required', 'matchRegexp:^.{0,32}$']}
 | 
			
		||||
                errorMessages={['Access Point SSID is required', 'Access Point SSID must be 32 characeters or less']}
 | 
			
		||||
                name="ssid"
 | 
			
		||||
                label="Access Point SSID"
 | 
			
		||||
                className={classes.textField}
 | 
			
		||||
                value={apSettings.ssid}
 | 
			
		||||
                onChange={handleValueChange('ssid')}
 | 
			
		||||
                margin="normal"
 | 
			
		||||
              />,
 | 
			
		||||
              <TextValidator key="password"
 | 
			
		||||
                    validators={['required', 'matchRegexp:^.{0,64}$']}
 | 
			
		||||
                    errorMessages={['Access Point Password is required', 'Access Point Password must be 64 characters or less']}
 | 
			
		||||
                    name="password"
 | 
			
		||||
                    label="Access Point Password"
 | 
			
		||||
                    className={classes.textField}
 | 
			
		||||
                    value={apSettings.password}
 | 
			
		||||
                    onChange={handleValueChange('password')}
 | 
			
		||||
                    margin="normal"
 | 
			
		||||
              />
 | 
			
		||||
            ]
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          <Button variant="raised" color="primary" className={classes.button} type="submit">
 | 
			
		||||
            Save
 | 
			
		||||
          </Button>
 | 
			
		||||
          <Button variant="raised" color="secondary" className={classes.button} onClick={onReset}>
 | 
			
		||||
      		  Reset
 | 
			
		||||
      		</Button>
 | 
			
		||||
 | 
			
		||||
        </ValidatorForm>
 | 
			
		||||
 | 
			
		||||
        :
 | 
			
		||||
 | 
			
		||||
        <div className={classes.loadingSettings}>
 | 
			
		||||
          <Typography variant="display1" className={classes.loadingSettingsDetails}>
 | 
			
		||||
            {errorMessage}
 | 
			
		||||
          </Typography>
 | 
			
		||||
          <Button variant="raised" color="secondary" className={classes.button} onClick={onReset}>
 | 
			
		||||
      		  Reset
 | 
			
		||||
      		</Button>
 | 
			
		||||
        </div>
 | 
			
		||||
      }
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APSettingsForm.propTypes = {
 | 
			
		||||
  classes: PropTypes.object.isRequired,
 | 
			
		||||
  apSettingsFetched: PropTypes.bool.isRequired,
 | 
			
		||||
  apSettings: PropTypes.object,
 | 
			
		||||
  errorMessage: PropTypes.string,
 | 
			
		||||
  onSubmit: PropTypes.func.isRequired,
 | 
			
		||||
  onReset: PropTypes.func.isRequired,
 | 
			
		||||
  handleValueChange: PropTypes.func.isRequired
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default withStyles(styles)(APSettingsForm);
 | 
			
		||||
							
								
								
									
										113
									
								
								interface/src/forms/NTPSettingsForm.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								interface/src/forms/NTPSettingsForm.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
 | 
			
		||||
import { withStyles } from 'material-ui/styles';
 | 
			
		||||
import Button from 'material-ui/Button';
 | 
			
		||||
import { LinearProgress } from 'material-ui/Progress';
 | 
			
		||||
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
 | 
			
		||||
import Typography from 'material-ui/Typography';
 | 
			
		||||
 | 
			
		||||
import isIP from '../validators/isIP';
 | 
			
		||||
import isHostname from '../validators/isHostname';
 | 
			
		||||
import or from '../validators/or';
 | 
			
		||||
 | 
			
		||||
const styles = theme => ({
 | 
			
		||||
  loadingSettings: {
 | 
			
		||||
    margin: theme.spacing.unit,
 | 
			
		||||
  },
 | 
			
		||||
  loadingSettingsDetails: {
 | 
			
		||||
    margin: theme.spacing.unit * 4,
 | 
			
		||||
    textAlign: "center"
 | 
			
		||||
  },
 | 
			
		||||
  textField: {
 | 
			
		||||
    width: "100%"
 | 
			
		||||
  },
 | 
			
		||||
  button: {
 | 
			
		||||
    marginRight: theme.spacing.unit * 2,
 | 
			
		||||
    marginTop: theme.spacing.unit * 2,
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
class NTPSettingsForm extends React.Component {
 | 
			
		||||
 | 
			
		||||
  componentWillMount() {
 | 
			
		||||
    ValidatorForm.addValidationRule('isIPOrHostname', or(isIP, isHostname));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const { classes, ntpSettingsFetched, ntpSettings, errorMessage, handleValueChange, onSubmit, onReset } = this.props;
 | 
			
		||||
    return (
 | 
			
		||||
      <div>
 | 
			
		||||
        {
 | 
			
		||||
         !ntpSettingsFetched ?
 | 
			
		||||
 | 
			
		||||
         <div className={classes.loadingSettings}>
 | 
			
		||||
           <LinearProgress className={classes.loadingSettingsDetails}/>
 | 
			
		||||
           <Typography variant="display1" className={classes.loadingSettingsDetails}>
 | 
			
		||||
             Loading...
 | 
			
		||||
           </Typography>
 | 
			
		||||
         </div>
 | 
			
		||||
 | 
			
		||||
         : ntpSettings ?
 | 
			
		||||
 | 
			
		||||
      	 <ValidatorForm onSubmit={onSubmit}>
 | 
			
		||||
 | 
			
		||||
           <TextValidator
 | 
			
		||||
               validators={['required', 'isIPOrHostname']}
 | 
			
		||||
               errorMessages={['Server is required', "Not a valid IP address or hostname"]}
 | 
			
		||||
               name="server"
 | 
			
		||||
               label="Server"
 | 
			
		||||
               className={classes.textField}
 | 
			
		||||
               value={ntpSettings.server}
 | 
			
		||||
               onChange={handleValueChange('server')}
 | 
			
		||||
               margin="normal"
 | 
			
		||||
             />
 | 
			
		||||
 | 
			
		||||
             <TextValidator
 | 
			
		||||
                 validators={['required','isNumber','minNumber:60','maxNumber:86400']}
 | 
			
		||||
                 errorMessages={['Interval is required','Interval must be a number','Must be at least 60 seconds',"Must not be more than 86400 seconds (24 hours)"]}
 | 
			
		||||
                 name="interval"
 | 
			
		||||
                 label="Interval (Seconds)"
 | 
			
		||||
                 className={classes.textField}
 | 
			
		||||
                 value={ntpSettings.interval}
 | 
			
		||||
                 type="number"
 | 
			
		||||
                 onChange={handleValueChange('interval')}
 | 
			
		||||
                 margin="normal"
 | 
			
		||||
               />
 | 
			
		||||
 | 
			
		||||
          <Button variant="raised" color="primary" className={classes.button} type="submit">
 | 
			
		||||
            Save
 | 
			
		||||
          </Button>
 | 
			
		||||
          <Button variant="raised" color="secondary" className={classes.button} onClick={onReset}>
 | 
			
		||||
      		  Reset
 | 
			
		||||
      		</Button>
 | 
			
		||||
 | 
			
		||||
         </ValidatorForm>
 | 
			
		||||
 | 
			
		||||
        :
 | 
			
		||||
 | 
			
		||||
        <div className={classes.loadingSettings}>
 | 
			
		||||
          <Typography variant="display1" className={classes.loadingSettingsDetails}>
 | 
			
		||||
            {errorMessage}
 | 
			
		||||
          </Typography>
 | 
			
		||||
          <Button variant="raised" color="secondary" className={classes.button} onClick={onReset}>
 | 
			
		||||
      		  Reset
 | 
			
		||||
      		</Button>
 | 
			
		||||
        </div>
 | 
			
		||||
      }
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NTPSettingsForm.propTypes = {
 | 
			
		||||
  classes: PropTypes.object.isRequired,
 | 
			
		||||
  ntpSettingsFetched: PropTypes.bool.isRequired,
 | 
			
		||||
  ntpSettings: PropTypes.object,
 | 
			
		||||
  errorMessage: PropTypes.string,
 | 
			
		||||
  onSubmit: PropTypes.func.isRequired,
 | 
			
		||||
  onReset: PropTypes.func.isRequired,
 | 
			
		||||
  handleValueChange: PropTypes.func.isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default withStyles(styles)(NTPSettingsForm);
 | 
			
		||||
							
								
								
									
										132
									
								
								interface/src/forms/OTASettingsForm.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								interface/src/forms/OTASettingsForm.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
 | 
			
		||||
import { withStyles } from 'material-ui/styles';
 | 
			
		||||
import Button from 'material-ui/Button';
 | 
			
		||||
import Switch from 'material-ui/Switch';
 | 
			
		||||
import { LinearProgress } from 'material-ui/Progress';
 | 
			
		||||
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
 | 
			
		||||
import Typography from 'material-ui/Typography';
 | 
			
		||||
import { FormControlLabel } from 'material-ui/Form';
 | 
			
		||||
 | 
			
		||||
import isIP from '../validators/isIP';
 | 
			
		||||
import isHostname from '../validators/isHostname';
 | 
			
		||||
import or from '../validators/or';
 | 
			
		||||
 | 
			
		||||
const styles = theme => ({
 | 
			
		||||
  loadingSettings: {
 | 
			
		||||
    margin: theme.spacing.unit,
 | 
			
		||||
  },
 | 
			
		||||
  loadingSettingsDetails: {
 | 
			
		||||
    margin: theme.spacing.unit * 4,
 | 
			
		||||
    textAlign: "center"
 | 
			
		||||
  },
 | 
			
		||||
  switchControl: {
 | 
			
		||||
    width: "100%",
 | 
			
		||||
    marginTop: theme.spacing.unit * 2,
 | 
			
		||||
    marginBottom: theme.spacing.unit
 | 
			
		||||
  },
 | 
			
		||||
  textField: {
 | 
			
		||||
    width: "100%"
 | 
			
		||||
  },
 | 
			
		||||
  button: {
 | 
			
		||||
    marginRight: theme.spacing.unit * 2,
 | 
			
		||||
    marginTop: theme.spacing.unit * 2,
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
class OTASettingsForm extends React.Component {
 | 
			
		||||
 | 
			
		||||
  componentWillMount() {
 | 
			
		||||
    ValidatorForm.addValidationRule('isIPOrHostname', or(isIP, isHostname));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const { classes, otaSettingsFetched, otaSettings, errorMessage, handleValueChange, handleCheckboxChange, onSubmit, onReset } = this.props;
 | 
			
		||||
    return (
 | 
			
		||||
      <div>
 | 
			
		||||
        {
 | 
			
		||||
         !otaSettingsFetched ?
 | 
			
		||||
 | 
			
		||||
         <div className={classes.loadingSettings}>
 | 
			
		||||
           <LinearProgress className={classes.loadingSettingsDetails}/>
 | 
			
		||||
           <Typography variant="display1" className={classes.loadingSettingsDetails}>
 | 
			
		||||
             Loading...
 | 
			
		||||
           </Typography>
 | 
			
		||||
         </div>
 | 
			
		||||
 | 
			
		||||
         : otaSettings ?
 | 
			
		||||
 | 
			
		||||
      	 <ValidatorForm onSubmit={onSubmit}>
 | 
			
		||||
 | 
			
		||||
            <FormControlLabel className={classes.switchControl}
 | 
			
		||||
               control={
 | 
			
		||||
                 <Switch
 | 
			
		||||
                        checked={otaSettings.enabled}
 | 
			
		||||
                        onChange={handleCheckboxChange('enabled')}
 | 
			
		||||
                        value="enabled"
 | 
			
		||||
                 />
 | 
			
		||||
               }
 | 
			
		||||
              label="Enable OTA Updates?"
 | 
			
		||||
             />
 | 
			
		||||
 | 
			
		||||
           <TextValidator
 | 
			
		||||
               validators={['required', 'isNumber', 'minNumber:1025', 'maxNumber:65535']}
 | 
			
		||||
               errorMessages={['Port is required', "Must be a number", "Must be greater than 1024 ", "Max value is 65535"]}
 | 
			
		||||
               name="port"
 | 
			
		||||
               label="Port"
 | 
			
		||||
               className={classes.textField}
 | 
			
		||||
               value={otaSettings.port}
 | 
			
		||||
               type="number"
 | 
			
		||||
               onChange={handleValueChange('port')}
 | 
			
		||||
               margin="normal"
 | 
			
		||||
             />
 | 
			
		||||
 | 
			
		||||
             <TextValidator key="password"
 | 
			
		||||
                   validators={['required', 'matchRegexp:^.{0,64}$']}
 | 
			
		||||
                   errorMessages={['OTA Password is required', 'OTA Point Password must be 64 characters or less']}
 | 
			
		||||
                   name="password"
 | 
			
		||||
                   label="Password"
 | 
			
		||||
                   className={classes.textField}
 | 
			
		||||
                   value={otaSettings.password}
 | 
			
		||||
                   onChange={handleValueChange('password')}
 | 
			
		||||
                   margin="normal"
 | 
			
		||||
             />
 | 
			
		||||
 | 
			
		||||
          <Button variant="raised" color="primary" className={classes.button} type="submit">
 | 
			
		||||
            Save
 | 
			
		||||
          </Button>
 | 
			
		||||
          <Button variant="raised" color="secondary" className={classes.button} onClick={onReset}>
 | 
			
		||||
      		  Reset
 | 
			
		||||
      		</Button>
 | 
			
		||||
 | 
			
		||||
         </ValidatorForm>
 | 
			
		||||
 | 
			
		||||
        :
 | 
			
		||||
 | 
			
		||||
        <div className={classes.loadingSettings}>
 | 
			
		||||
          <Typography variant="display1" className={classes.loadingSettingsDetails}>
 | 
			
		||||
            {errorMessage}
 | 
			
		||||
          </Typography>
 | 
			
		||||
          <Button variant="raised" color="secondary" className={classes.button} onClick={onReset}>
 | 
			
		||||
      		  Reset
 | 
			
		||||
      		</Button>
 | 
			
		||||
        </div>
 | 
			
		||||
      }
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
OTASettingsForm.propTypes = {
 | 
			
		||||
  classes: PropTypes.object.isRequired,
 | 
			
		||||
  otaSettingsFetched: PropTypes.bool.isRequired,
 | 
			
		||||
  otaSettings: PropTypes.object,
 | 
			
		||||
  errorMessage: PropTypes.string,
 | 
			
		||||
  onSubmit: PropTypes.func.isRequired,
 | 
			
		||||
  onReset: PropTypes.func.isRequired,
 | 
			
		||||
  handleValueChange: PropTypes.func.isRequired,
 | 
			
		||||
  handleCheckboxChange: PropTypes.func.isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default withStyles(styles)(OTASettingsForm);
 | 
			
		||||
							
								
								
									
										102
									
								
								interface/src/forms/WiFiNetworkSelector.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								interface/src/forms/WiFiNetworkSelector.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
import React, { Component } from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
 | 
			
		||||
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 { isNetworkOpen, networkSecurityMode } from '../constants/WiFiSecurityModes';
 | 
			
		||||
 | 
			
		||||
import List, { ListItem, ListItemText, ListItemIcon,  ListItemAvatar } from 'material-ui/List';
 | 
			
		||||
import Avatar from 'material-ui/Avatar';
 | 
			
		||||
import Badge from 'material-ui/Badge';
 | 
			
		||||
 | 
			
		||||
import WifiIcon from 'material-ui-icons/Wifi';
 | 
			
		||||
import LockIcon from 'material-ui-icons/Lock';
 | 
			
		||||
import LockOpenIcon from 'material-ui-icons/LockOpen';
 | 
			
		||||
 | 
			
		||||
const styles = theme => ({
 | 
			
		||||
  scanningProgress: {
 | 
			
		||||
    margin: theme.spacing.unit * 4,
 | 
			
		||||
    textAlign: "center"
 | 
			
		||||
  },
 | 
			
		||||
  button: {
 | 
			
		||||
    marginRight: theme.spacing.unit * 2,
 | 
			
		||||
    marginTop: theme.spacing.unit * 2,
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
class WiFiNetworkSelector extends Component {
 | 
			
		||||
 | 
			
		||||
  constructor(props) {
 | 
			
		||||
    super(props);
 | 
			
		||||
 | 
			
		||||
    this.renderNetwork = this.renderNetwork.bind(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  renderNetwork(network) {
 | 
			
		||||
    return ([
 | 
			
		||||
        <ListItem key={network.ssid} button onClick={() => this.props.selectNetwork(network)}>
 | 
			
		||||
          <ListItemAvatar>
 | 
			
		||||
            <Avatar>
 | 
			
		||||
              {isNetworkOpen(network) ? <LockOpenIcon /> : <LockIcon />}
 | 
			
		||||
            </Avatar>
 | 
			
		||||
          </ListItemAvatar>
 | 
			
		||||
          <ListItemText
 | 
			
		||||
            primary={network.ssid}
 | 
			
		||||
            secondary={"Security: "+ networkSecurityMode(network) + ", Ch: " + network.channel}
 | 
			
		||||
          />
 | 
			
		||||
          <ListItemIcon>
 | 
			
		||||
            <Badge badgeContent={network.rssi + "db"}>
 | 
			
		||||
              <WifiIcon />
 | 
			
		||||
            </Badge>
 | 
			
		||||
          </ListItemIcon>
 | 
			
		||||
        </ListItem>
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const { classes, scanningForNetworks, networkList, errorMessage, requestNetworkScan } = this.props;
 | 
			
		||||
    return (
 | 
			
		||||
      <div>
 | 
			
		||||
        {
 | 
			
		||||
          scanningForNetworks ?
 | 
			
		||||
          <div>
 | 
			
		||||
            <LinearProgress className={classes.scanningProgress}/>
 | 
			
		||||
            <Typography variant="display1" className={classes.scanningProgress}>
 | 
			
		||||
              Scanning...
 | 
			
		||||
            </Typography>
 | 
			
		||||
          </div>
 | 
			
		||||
          :
 | 
			
		||||
          networkList ?
 | 
			
		||||
          <List>
 | 
			
		||||
            {networkList.networks.map(this.renderNetwork)}
 | 
			
		||||
          </List>
 | 
			
		||||
          :
 | 
			
		||||
          <div>
 | 
			
		||||
            <Typography variant="display1" className={classes.scanningProgress}>
 | 
			
		||||
              {errorMessage}
 | 
			
		||||
            </Typography>
 | 
			
		||||
          </div>
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        <Button variant="raised" color="secondary" className={classes.button} onClick={requestNetworkScan} disabled={scanningForNetworks}>
 | 
			
		||||
          Scan again...
 | 
			
		||||
        </Button>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
WiFiNetworkSelector.propTypes = {
 | 
			
		||||
  classes: PropTypes.object.isRequired,
 | 
			
		||||
  selectNetwork: PropTypes.func.isRequired,
 | 
			
		||||
  scanningForNetworks: PropTypes.bool.isRequired,
 | 
			
		||||
  errorMessage: PropTypes.string,
 | 
			
		||||
  networkList: PropTypes.object,
 | 
			
		||||
  requestNetworkScan: PropTypes.func.isRequired
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default withStyles(styles)(WiFiNetworkSelector);
 | 
			
		||||
							
								
								
									
										237
									
								
								interface/src/forms/WiFiSettingsForm.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								interface/src/forms/WiFiSettingsForm.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,237 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
 | 
			
		||||
import { withStyles } from 'material-ui/styles';
 | 
			
		||||
import Button from 'material-ui/Button';
 | 
			
		||||
import { LinearProgress } from 'material-ui/Progress';
 | 
			
		||||
import Checkbox from 'material-ui/Checkbox';
 | 
			
		||||
import { FormControlLabel } from 'material-ui/Form';
 | 
			
		||||
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
 | 
			
		||||
import Typography from 'material-ui/Typography';
 | 
			
		||||
import List, { ListItem, ListItemText, ListItemSecondaryAction,  ListItemAvatar } from 'material-ui/List';
 | 
			
		||||
 | 
			
		||||
import { isNetworkOpen, networkSecurityMode } from '../constants/WiFiSecurityModes';
 | 
			
		||||
 | 
			
		||||
import Avatar from 'material-ui/Avatar';
 | 
			
		||||
import IconButton from 'material-ui/IconButton';
 | 
			
		||||
import LockIcon from 'material-ui-icons/Lock';
 | 
			
		||||
import LockOpenIcon from 'material-ui-icons/LockOpen';
 | 
			
		||||
import DeleteIcon from 'material-ui-icons/Delete';
 | 
			
		||||
 | 
			
		||||
import isIP from '../validators/isIP';
 | 
			
		||||
import isHostname from '../validators/isHostname';
 | 
			
		||||
import optional from '../validators/optional';
 | 
			
		||||
 | 
			
		||||
const styles = theme => ({
 | 
			
		||||
  loadingSettings: {
 | 
			
		||||
    margin: theme.spacing.unit,
 | 
			
		||||
  },
 | 
			
		||||
  loadingSettingsDetails: {
 | 
			
		||||
    margin: theme.spacing.unit * 4,
 | 
			
		||||
    textAlign: "center"
 | 
			
		||||
  },
 | 
			
		||||
  textField: {
 | 
			
		||||
    width: "100%"
 | 
			
		||||
  },
 | 
			
		||||
  checkboxControl: {
 | 
			
		||||
    width: "100%"
 | 
			
		||||
  },
 | 
			
		||||
  button: {
 | 
			
		||||
    marginRight: theme.spacing.unit * 2,
 | 
			
		||||
    marginTop: theme.spacing.unit * 2,
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
class WiFiSettingsForm extends React.Component {
 | 
			
		||||
 | 
			
		||||
  componentWillMount() {
 | 
			
		||||
    ValidatorForm.addValidationRule('isIP', isIP);
 | 
			
		||||
    ValidatorForm.addValidationRule('isHostname', isHostname);
 | 
			
		||||
    ValidatorForm.addValidationRule('isOptionalIP', optional(isIP));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  renderSelectedNetwork() {
 | 
			
		||||
    const { selectedNetwork, deselectNetwork } = this.props;
 | 
			
		||||
    return (
 | 
			
		||||
      <List>
 | 
			
		||||
        <ListItem key={selectedNetwork.ssid}>
 | 
			
		||||
          <ListItemAvatar>
 | 
			
		||||
            <Avatar>
 | 
			
		||||
              {isNetworkOpen(selectedNetwork) ? <LockOpenIcon /> : <LockIcon />}
 | 
			
		||||
            </Avatar>
 | 
			
		||||
          </ListItemAvatar>
 | 
			
		||||
          <ListItemText
 | 
			
		||||
            primary={selectedNetwork.ssid}
 | 
			
		||||
            secondary={"Security: "+ networkSecurityMode(selectedNetwork) + ", Ch: " + selectedNetwork.channel}
 | 
			
		||||
          />
 | 
			
		||||
          <ListItemSecondaryAction>
 | 
			
		||||
            <IconButton aria-label="Manual Config" onClick={deselectNetwork}>
 | 
			
		||||
              <DeleteIcon />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
          </ListItemSecondaryAction>
 | 
			
		||||
        </ListItem>
 | 
			
		||||
      </List>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const { classes, formRef, wifiSettingsFetched, wifiSettings, errorMessage, selectedNetwork, handleValueChange, handleCheckboxChange, onSubmit, onReset } = this.props;
 | 
			
		||||
    return (
 | 
			
		||||
      <div ref={formRef}>
 | 
			
		||||
        {
 | 
			
		||||
         !wifiSettingsFetched ?
 | 
			
		||||
 | 
			
		||||
         <div className={classes.loadingSettings}>
 | 
			
		||||
           <LinearProgress className={classes.loadingSettingsDetails}/>
 | 
			
		||||
           <Typography variant="display1" className={classes.loadingSettingsDetails}>
 | 
			
		||||
             Loading...
 | 
			
		||||
           </Typography>
 | 
			
		||||
         </div>
 | 
			
		||||
 | 
			
		||||
         : wifiSettings ?
 | 
			
		||||
 | 
			
		||||
      	 <ValidatorForm onSubmit={onSubmit} ref="WiFiSettingsForm">
 | 
			
		||||
           {
 | 
			
		||||
             selectedNetwork ? this.renderSelectedNetwork() :
 | 
			
		||||
              <TextValidator
 | 
			
		||||
                validators={['required', 'matchRegexp:^.{0,32}$']}
 | 
			
		||||
                errorMessages={['SSID is required', 'SSID must be 32 characeters or less']}
 | 
			
		||||
                name="ssid"
 | 
			
		||||
                label="SSID"
 | 
			
		||||
                className={classes.textField}
 | 
			
		||||
                value={wifiSettings.ssid}
 | 
			
		||||
                onChange={handleValueChange('ssid')}
 | 
			
		||||
                margin="normal"
 | 
			
		||||
              />
 | 
			
		||||
            }
 | 
			
		||||
              {
 | 
			
		||||
                !isNetworkOpen(selectedNetwork) &&
 | 
			
		||||
         		<TextValidator
 | 
			
		||||
                  validators={['matchRegexp:^.{0,64}$']}
 | 
			
		||||
                  errorMessages={['Password must be 64 characters or less']}
 | 
			
		||||
                  name="password"
 | 
			
		||||
                  label="Password"
 | 
			
		||||
                  className={classes.textField}
 | 
			
		||||
                  value={wifiSettings.password}
 | 
			
		||||
                  onChange={handleValueChange('password')}
 | 
			
		||||
                  margin="normal"
 | 
			
		||||
                />
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              <TextValidator
 | 
			
		||||
                    validators={['required', 'isHostname']}
 | 
			
		||||
                    errorMessages={['Hostname is required', "Not a valid hostname"]}
 | 
			
		||||
                    name="hostname"
 | 
			
		||||
                    label="Hostname"
 | 
			
		||||
                    className={classes.textField}
 | 
			
		||||
                    value={wifiSettings.hostname}
 | 
			
		||||
                    onChange={handleValueChange('hostname')}
 | 
			
		||||
                    margin="normal"
 | 
			
		||||
                  />
 | 
			
		||||
 | 
			
		||||
        		<FormControlLabel className={classes.checkboxControl}
 | 
			
		||||
                  control={
 | 
			
		||||
                    <Checkbox
 | 
			
		||||
                      value="static_ip_config"
 | 
			
		||||
                      checked={wifiSettings.static_ip_config}
 | 
			
		||||
                      onChange={handleCheckboxChange("static_ip_config")}
 | 
			
		||||
                    />
 | 
			
		||||
                  }
 | 
			
		||||
                  label="Static IP Config?"
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
        		{
 | 
			
		||||
          		wifiSettings.static_ip_config &&
 | 
			
		||||
          		[
 | 
			
		||||
          			<TextValidator key="local_ip"
 | 
			
		||||
          			  validators={['required', 'isIP']}
 | 
			
		||||
          			  errorMessages={['Local IP is required', 'Must be an IP address']}
 | 
			
		||||
          			  name="local_ip"
 | 
			
		||||
          			  label="Local IP"
 | 
			
		||||
          			  className={classes.textField}
 | 
			
		||||
          			  value={wifiSettings.local_ip}
 | 
			
		||||
          			  onChange={handleValueChange('local_ip')}
 | 
			
		||||
          			  margin="normal"
 | 
			
		||||
          			/>,
 | 
			
		||||
                <TextValidator key="gateway_ip"
 | 
			
		||||
                  validators={['required', 'isIP']}
 | 
			
		||||
                  errorMessages={['Gateway IP is required', 'Must be an IP address']}
 | 
			
		||||
                  name="gateway_ip"
 | 
			
		||||
                  label="Gateway"
 | 
			
		||||
                  className={classes.textField}
 | 
			
		||||
                  value={wifiSettings.gateway_ip}
 | 
			
		||||
                  onChange={handleValueChange('gateway_ip')}
 | 
			
		||||
                  margin="normal"
 | 
			
		||||
                />,
 | 
			
		||||
                <TextValidator key="subnet_mask"
 | 
			
		||||
          			  validators={['required', 'isIP']}
 | 
			
		||||
          			  errorMessages={['Subnet mask is required', 'Must be an IP address']}
 | 
			
		||||
          			  name="subnet_mask"
 | 
			
		||||
          			  label="Subnet"
 | 
			
		||||
          			  className={classes.textField}
 | 
			
		||||
          			  value={wifiSettings.subnet_mask}
 | 
			
		||||
          			  onChange={handleValueChange('subnet_mask')}
 | 
			
		||||
                  margin="normal"
 | 
			
		||||
          			/>,
 | 
			
		||||
                <TextValidator key="dns_ip_1"
 | 
			
		||||
          			  validators={['isOptionalIP']}
 | 
			
		||||
          			  errorMessages={['Must be an IP address']}
 | 
			
		||||
          			  name="dns_ip_1"
 | 
			
		||||
          			  label="DNS IP #1"
 | 
			
		||||
          			  className={classes.textField}
 | 
			
		||||
          			  value={wifiSettings.dns_ip_1}
 | 
			
		||||
          			  onChange={handleValueChange('dns_ip_1')}
 | 
			
		||||
                  margin="normal"
 | 
			
		||||
          			/>,
 | 
			
		||||
                <TextValidator key="dns_ip_2"
 | 
			
		||||
          			  validators={['isOptionalIP']}
 | 
			
		||||
          			  errorMessages={['Must be an IP address']}
 | 
			
		||||
          			  name="dns_ip_2"
 | 
			
		||||
          			  label="DNS IP #2"
 | 
			
		||||
          			  className={classes.textField}
 | 
			
		||||
          			  value={wifiSettings.dns_ip_2}
 | 
			
		||||
          			  onChange={handleValueChange('dns_ip_2')}
 | 
			
		||||
                  margin="normal"
 | 
			
		||||
          			/>
 | 
			
		||||
          		]
 | 
			
		||||
      		  }
 | 
			
		||||
 | 
			
		||||
          <Button variant="raised" color="primary" className={classes.button} type="submit">
 | 
			
		||||
            Save
 | 
			
		||||
          </Button>
 | 
			
		||||
          <Button variant="raised" color="secondary" className={classes.button} onClick={onReset}>
 | 
			
		||||
      		  Reset
 | 
			
		||||
      		</Button>
 | 
			
		||||
 | 
			
		||||
        </ValidatorForm>
 | 
			
		||||
 | 
			
		||||
        :
 | 
			
		||||
 | 
			
		||||
        <div className={classes.loadingSettings}>
 | 
			
		||||
          <Typography variant="display1" className={classes.loadingSettingsDetails}>
 | 
			
		||||
            {errorMessage}
 | 
			
		||||
          </Typography>
 | 
			
		||||
          <Button variant="raised" color="secondary" className={classes.button} onClick={onReset}>
 | 
			
		||||
      		  Reset
 | 
			
		||||
      		</Button>
 | 
			
		||||
        </div>
 | 
			
		||||
      }
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
WiFiSettingsForm.propTypes = {
 | 
			
		||||
  classes: PropTypes.object.isRequired,
 | 
			
		||||
  wifiSettingsFetched: PropTypes.bool.isRequired,
 | 
			
		||||
  wifiSettings: PropTypes.object,
 | 
			
		||||
  errorMessage: PropTypes.string,
 | 
			
		||||
  deselectNetwork: PropTypes.func,
 | 
			
		||||
  selectedNetwork: PropTypes.object,
 | 
			
		||||
  onSubmit: PropTypes.func.isRequired,
 | 
			
		||||
  onReset: PropTypes.func.isRequired,
 | 
			
		||||
  handleValueChange: PropTypes.func.isRequired,
 | 
			
		||||
  handleCheckboxChange: PropTypes.func.isRequired
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default withStyles(styles)(WiFiSettingsForm);
 | 
			
		||||
							
								
								
									
										35
									
								
								interface/src/helpers/SimpleGet.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								interface/src/helpers/SimpleGet.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
/**
 | 
			
		||||
* Executes a get request for an endpoint, updating the local state of the calling
 | 
			
		||||
* component. The calling component must bind setState before using this
 | 
			
		||||
* function.
 | 
			
		||||
*
 | 
			
		||||
* This is designed for re-use in simple situations, we arn't using redux here!
 | 
			
		||||
*/
 | 
			
		||||
export const simpleGet = (
 | 
			
		||||
  endpointUrl,
 | 
			
		||||
  setState,
 | 
			
		||||
  raiseNotification = null,
 | 
			
		||||
  dataKey="status",
 | 
			
		||||
  fetchedKey="fetched",
 | 
			
		||||
  errorMessageKey = "errorMessage"
 | 
			
		||||
) => {
 | 
			
		||||
  setState({
 | 
			
		||||
           [dataKey]:null,
 | 
			
		||||
           [fetchedKey]: false,
 | 
			
		||||
           [errorMessageKey]:null
 | 
			
		||||
         });
 | 
			
		||||
  fetch(endpointUrl)
 | 
			
		||||
    .then(response => {
 | 
			
		||||
      if (response.status === 200) {
 | 
			
		||||
        return response.json();
 | 
			
		||||
      }
 | 
			
		||||
      throw Error("Invalid status code: " + response.status);
 | 
			
		||||
    })
 | 
			
		||||
    .then(json => {setState({[dataKey]: json, [fetchedKey]:true})})
 | 
			
		||||
    .catch(error =>{
 | 
			
		||||
      if (raiseNotification) {
 | 
			
		||||
        raiseNotification("Problem fetching. " + error.message);
 | 
			
		||||
      }
 | 
			
		||||
      setState({[dataKey]: null, [fetchedKey]:true, [errorMessageKey]:error.message});
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								interface/src/helpers/SimplePost.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								interface/src/helpers/SimplePost.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
/**
 | 
			
		||||
* Executes a post request for saving data to an endpoint, updating the local
 | 
			
		||||
* state with the response. The calling component must bind setState before
 | 
			
		||||
* using this function.
 | 
			
		||||
*
 | 
			
		||||
* This is designed for re-use in simple situations, we arn't using redux here!
 | 
			
		||||
*/
 | 
			
		||||
export const simplePost = (
 | 
			
		||||
  endpointUrl,
 | 
			
		||||
  state,
 | 
			
		||||
  setState,
 | 
			
		||||
  raiseNotification = null,
 | 
			
		||||
  dataKey="settings",
 | 
			
		||||
  fetchedKey="fetched",
 | 
			
		||||
  errorMessageKey = "errorMessage"
 | 
			
		||||
) => {
 | 
			
		||||
  setState({[fetchedKey]: false});
 | 
			
		||||
  fetch(endpointUrl, {
 | 
			
		||||
    method: 'POST',
 | 
			
		||||
    body: JSON.stringify(state[dataKey]),
 | 
			
		||||
    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 => {
 | 
			
		||||
    raiseNotification("Changes successfully applied.");
 | 
			
		||||
    setState({[dataKey]: json, [fetchedKey]:true});
 | 
			
		||||
  }).catch(error => {
 | 
			
		||||
    raiseNotification("Problem saving. " + error.message);
 | 
			
		||||
    setState({[dataKey]: null, [fetchedKey]:true, [errorMessageKey]:error.message});
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								interface/src/history.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								interface/src/history.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
import { createBrowserHistory } from 'history';
 | 
			
		||||
 | 
			
		||||
export default createBrowserHistory({
 | 
			
		||||
  /* pass a configuration object here if needed */
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										16
									
								
								interface/src/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								interface/src/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { render } from 'react-dom';
 | 
			
		||||
 | 
			
		||||
import history from './history';
 | 
			
		||||
import { Router, Route, Redirect, Switch } from 'react-router';
 | 
			
		||||
 | 
			
		||||
import App from './App';
 | 
			
		||||
 | 
			
		||||
render((
 | 
			
		||||
  <Router history={history}>
 | 
			
		||||
    <Switch>
 | 
			
		||||
      <Redirect exact from='/' to='/home'/>
 | 
			
		||||
      <Route path="/" component={App} />
 | 
			
		||||
    </Switch>
 | 
			
		||||
  </Router>
 | 
			
		||||
), document.getElementById("root"))
 | 
			
		||||
							
								
								
									
										6
									
								
								interface/src/validators/isHostname.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								interface/src/validators/isHostname.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
const hostnameLengthRegex = /^.{0,32}$/
 | 
			
		||||
const hostnamePatternRegex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/
 | 
			
		||||
 | 
			
		||||
export default function isHostname(hostname) {
 | 
			
		||||
  return hostnameLengthRegex.test(hostname) && hostnamePatternRegex.test(hostname);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								interface/src/validators/isIP.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								interface/src/validators/isIP.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
const ipAddressRegexp = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
 | 
			
		||||
 | 
			
		||||
export default function isIp(ipAddress) {
 | 
			
		||||
  return ipAddressRegexp.test(ipAddress);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								interface/src/validators/optional.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								interface/src/validators/optional.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
export default validator => value => !value || validator(value);
 | 
			
		||||
							
								
								
									
										1
									
								
								interface/src/validators/or.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								interface/src/validators/or.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
export default (validator1, validator2) => value => validator1(value) || validator2(value);
 | 
			
		||||
		Reference in New Issue
	
	Block a user