mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-11 10:19:55 +03:00
add Ethernet
This commit is contained in:
62
interface/src/network/NetworkConnection.tsx
Normal file
62
interface/src/network/NetworkConnection.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'
|
||||
|
||||
import { Tabs, Tab } from '@material-ui/core';
|
||||
|
||||
import { withAuthenticatedContext, AuthenticatedContextProps, AuthenticatedRoute } from '../authentication';
|
||||
import { MenuAppBar } from '../components';
|
||||
|
||||
import NetworkStatusController from './NetworkStatusController';
|
||||
import NetworkSettingsController from './NetworkSettingsController';
|
||||
import WiFiNetworkScanner from './WiFiNetworkScanner';
|
||||
import { NetworkConnectionContext } from './NetworkConnectionContext';
|
||||
import { WiFiNetwork } from './types';
|
||||
|
||||
type NetworkConnectionProps = AuthenticatedContextProps & RouteComponentProps;
|
||||
|
||||
class NetworkConnection extends Component<NetworkConnectionProps, NetworkConnectionContext> {
|
||||
|
||||
constructor(props: NetworkConnectionProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectNetwork: this.selectNetwork,
|
||||
deselectNetwork: this.deselectNetwork
|
||||
};
|
||||
}
|
||||
|
||||
selectNetwork = (network: WiFiNetwork) => {
|
||||
this.setState({ selectedNetwork: network });
|
||||
this.props.history.push('/network/settings');
|
||||
}
|
||||
|
||||
deselectNetwork = () => {
|
||||
this.setState({ selectedNetwork: undefined });
|
||||
}
|
||||
|
||||
handleTabChange = (event: React.ChangeEvent<{}>, path: string) => {
|
||||
this.props.history.push(path);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { authenticatedContext } = this.props;
|
||||
return (
|
||||
<NetworkConnectionContext.Provider value={this.state}>
|
||||
<MenuAppBar sectionTitle="Network Connection">
|
||||
<Tabs value={this.props.match.url} onChange={this.handleTabChange} variant="fullWidth">
|
||||
<Tab value="/network/status" label="Network Status" />
|
||||
<Tab value="/network/scan" label="Scan Networks" disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="/network/settings" label="Network Settings" disabled={!authenticatedContext.me.admin} />
|
||||
</Tabs>
|
||||
<Switch>
|
||||
<AuthenticatedRoute exact path="/network/status" component={NetworkStatusController} />
|
||||
<AuthenticatedRoute exact path="/network/scan" component={WiFiNetworkScanner} />
|
||||
<AuthenticatedRoute exact path="/network/settings" component={NetworkSettingsController} />
|
||||
<Redirect to="/network/status" />
|
||||
</Switch>
|
||||
</MenuAppBar>
|
||||
</NetworkConnectionContext.Provider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withAuthenticatedContext(NetworkConnection);
|
||||
13
interface/src/network/NetworkConnectionContext.tsx
Normal file
13
interface/src/network/NetworkConnectionContext.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import { WiFiNetwork } from './types';
|
||||
|
||||
export interface NetworkConnectionContext {
|
||||
selectedNetwork?: WiFiNetwork;
|
||||
selectNetwork: (network: WiFiNetwork) => void;
|
||||
deselectNetwork: () => void;
|
||||
}
|
||||
|
||||
const NetworkConnectionContextDefaultValue = {} as NetworkConnectionContext
|
||||
export const NetworkConnectionContext = React.createContext(
|
||||
NetworkConnectionContextDefaultValue
|
||||
);
|
||||
29
interface/src/network/NetworkSettingsController.tsx
Normal file
29
interface/src/network/NetworkSettingsController.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components';
|
||||
import NetworkSettingsForm from './NetworkSettingsForm';
|
||||
import { NETWORK_SETTINGS_ENDPOINT } from '../api';
|
||||
import { NetworkSettings } from './types';
|
||||
|
||||
type NetworkSettingsControllerProps = RestControllerProps<NetworkSettings>;
|
||||
|
||||
class NetworkSettingsController extends Component<NetworkSettingsControllerProps> {
|
||||
|
||||
componentDidMount() {
|
||||
this.props.loadData();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SectionContent title="Network Settings">
|
||||
<RestFormLoader
|
||||
{...this.props}
|
||||
render={formProps => <NetworkSettingsForm {...formProps} />}
|
||||
/>
|
||||
</SectionContent>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default restController(NETWORK_SETTINGS_ENDPOINT, NetworkSettingsController);
|
||||
213
interface/src/network/NetworkSettingsForm.tsx
Normal file
213
interface/src/network/NetworkSettingsForm.tsx
Normal file
@@ -0,0 +1,213 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { TextValidator, SelectValidator, ValidatorForm } from 'react-material-ui-form-validator';
|
||||
|
||||
import { Checkbox, List, ListItem, ListItemText, ListItemAvatar, ListItemSecondaryAction } from '@material-ui/core';
|
||||
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import LockIcon from '@material-ui/icons/Lock';
|
||||
import LockOpenIcon from '@material-ui/icons/LockOpen';
|
||||
import DeleteIcon from '@material-ui/icons/Delete';
|
||||
import SaveIcon from '@material-ui/icons/Save';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
|
||||
import { RestFormProps, PasswordValidator, BlockFormControlLabel, FormActions, FormButton } from '../components';
|
||||
import { isIP, isHostname, optional } from '../validators';
|
||||
|
||||
import { NetworkConnectionContext } from './NetworkConnectionContext';
|
||||
import { isNetworkOpen, networkSecurityMode } from './WiFiSecurityModes';
|
||||
import { NetworkSettings } from './types';
|
||||
|
||||
type NetworkStatusFormProps = RestFormProps<NetworkSettings>;
|
||||
|
||||
class NetworkSettingsForm extends React.Component<NetworkStatusFormProps> {
|
||||
|
||||
static contextType = NetworkConnectionContext;
|
||||
context!: React.ContextType<typeof NetworkConnectionContext>;
|
||||
|
||||
constructor(props: NetworkStatusFormProps, context: NetworkConnectionContext) {
|
||||
super(props);
|
||||
|
||||
const { selectedNetwork } = context;
|
||||
if (selectedNetwork) {
|
||||
const networkSettings: NetworkSettings = {
|
||||
ssid: selectedNetwork.ssid,
|
||||
password: "",
|
||||
hostname: props.data.hostname,
|
||||
ethernet_profile: 0,
|
||||
static_ip_config: false,
|
||||
}
|
||||
props.setData(networkSettings);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
ValidatorForm.addValidationRule('isIP', isIP);
|
||||
ValidatorForm.addValidationRule('isHostname', isHostname);
|
||||
ValidatorForm.addValidationRule('isOptionalIP', optional(isIP));
|
||||
}
|
||||
|
||||
deselectNetworkAndLoadData = () => {
|
||||
this.context.deselectNetwork();
|
||||
this.props.loadData();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.context.deselectNetwork();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selectedNetwork, deselectNetwork } = this.context;
|
||||
const { data, handleValueChange, saveData } = this.props;
|
||||
return (
|
||||
<ValidatorForm onSubmit={saveData} ref="NetworkSettingsForm">
|
||||
{
|
||||
selectedNetwork ?
|
||||
<List>
|
||||
<ListItem>
|
||||
<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>
|
||||
:
|
||||
<TextValidator
|
||||
validators={['matchRegexp:^.{0,32}$']}
|
||||
errorMessages={['SSID must be 32 characters or less']}
|
||||
name="ssid"
|
||||
label="SSID"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.ssid}
|
||||
onChange={handleValueChange('ssid')}
|
||||
margin="normal"
|
||||
/>
|
||||
}
|
||||
{
|
||||
(!selectedNetwork || !isNetworkOpen(selectedNetwork)) &&
|
||||
<PasswordValidator
|
||||
validators={['matchRegexp:^.{0,64}$']}
|
||||
errorMessages={['Password must be 64 characters or less']}
|
||||
name="password"
|
||||
label="Password"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.password}
|
||||
onChange={handleValueChange('password')}
|
||||
margin="normal"
|
||||
/>
|
||||
}
|
||||
<TextValidator
|
||||
validators={['required', 'isHostname']}
|
||||
errorMessages={['Hostname is required', "Not a valid hostname"]}
|
||||
name="hostname"
|
||||
label="Hostname"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.hostname}
|
||||
onChange={handleValueChange('hostname')}
|
||||
margin="normal"
|
||||
/>
|
||||
<SelectValidator name="ems_bus_id"
|
||||
label="Ethernet Profile"
|
||||
value={data.ethernet_profile}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={handleValueChange('ethernet_profile')}
|
||||
margin="normal">
|
||||
<MenuItem value={0}>None (wifi only)</MenuItem>
|
||||
<MenuItem value={1}>Profile 1 (LAN8720)</MenuItem>
|
||||
<MenuItem value={2}>Profile 2 (TLK110)</MenuItem>
|
||||
</SelectValidator>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
value="static_ip_config"
|
||||
checked={data.static_ip_config}
|
||||
onChange={handleValueChange("static_ip_config")}
|
||||
/>
|
||||
}
|
||||
label="Static IP Config"
|
||||
/>
|
||||
{
|
||||
data.static_ip_config &&
|
||||
<Fragment>
|
||||
<TextValidator
|
||||
validators={['required', 'isIP']}
|
||||
errorMessages={['Local IP is required', 'Must be an IP address']}
|
||||
name="local_ip"
|
||||
label="Local IP"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.local_ip}
|
||||
onChange={handleValueChange('local_ip')}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['required', 'isIP']}
|
||||
errorMessages={['Gateway IP is required', 'Must be an IP address']}
|
||||
name="gateway_ip"
|
||||
label="Gateway"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.gateway_ip}
|
||||
onChange={handleValueChange('gateway_ip')}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['required', 'isIP']}
|
||||
errorMessages={['Subnet mask is required', 'Must be an IP address']}
|
||||
name="subnet_mask"
|
||||
label="Subnet"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.subnet_mask}
|
||||
onChange={handleValueChange('subnet_mask')}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['isOptionalIP']}
|
||||
errorMessages={['Must be an IP address']}
|
||||
name="dns_ip_1"
|
||||
label="DNS IP #1"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.dns_ip_1}
|
||||
onChange={handleValueChange('dns_ip_1')}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['isOptionalIP']}
|
||||
errorMessages={['Must be an IP address']}
|
||||
name="dns_ip_2"
|
||||
label="DNS IP #2"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.dns_ip_2}
|
||||
onChange={handleValueChange('dns_ip_2')}
|
||||
margin="normal"
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
<FormActions>
|
||||
<FormButton startIcon={<SaveIcon />} variant="contained" color="primary" type="submit">
|
||||
Save
|
||||
</FormButton>
|
||||
</FormActions>
|
||||
</ValidatorForm>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NetworkSettingsForm;
|
||||
48
interface/src/network/NetworkStatus.ts
Normal file
48
interface/src/network/NetworkStatus.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Theme } from '@material-ui/core';
|
||||
import { NetworkStatus, NetworkConnectionStatus } from './types';
|
||||
|
||||
export const isConnected = ({ status }: NetworkStatus) => {
|
||||
return ((status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED) || (status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED));
|
||||
}
|
||||
|
||||
export const isWiFi = ({ status }: NetworkStatus) => (status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED)
|
||||
|
||||
export const networkStatusHighlight = ({ status }: NetworkStatus, theme: Theme) => {
|
||||
switch (status) {
|
||||
case NetworkConnectionStatus.WIFI_STATUS_IDLE:
|
||||
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
|
||||
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
|
||||
return theme.palette.info.main;
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
|
||||
case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED:
|
||||
return theme.palette.success.main;
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED:
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
|
||||
return theme.palette.error.main;
|
||||
default:
|
||||
return theme.palette.warning.main;
|
||||
}
|
||||
}
|
||||
|
||||
export const networkStatus = ({ status }: NetworkStatus) => {
|
||||
switch (status) {
|
||||
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
|
||||
return "Inactive";
|
||||
case NetworkConnectionStatus.WIFI_STATUS_IDLE:
|
||||
return "Idle";
|
||||
case NetworkConnectionStatus.WIFI_STATUS_NO_SSID_AVAIL:
|
||||
return "No SSID Available";
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
|
||||
return "Connected (WiFi)";
|
||||
case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED:
|
||||
return "Connected (Ethernet)";
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED:
|
||||
return "Connection Failed";
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
|
||||
return "Connection Lost";
|
||||
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
|
||||
return "Disconnected";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
29
interface/src/network/NetworkStatusController.tsx
Normal file
29
interface/src/network/NetworkStatusController.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import {restController, RestControllerProps, RestFormLoader, SectionContent } from '../components';
|
||||
import NetworkStatusForm from './NetworkStatusForm';
|
||||
import { NETWORK_STATUS_ENDPOINT } from '../api';
|
||||
import { NetworkStatus } from './types';
|
||||
|
||||
type NetworkStatusControllerProps = RestControllerProps<NetworkStatus>;
|
||||
|
||||
class NetworkStatusController extends Component<NetworkStatusControllerProps> {
|
||||
|
||||
componentDidMount() {
|
||||
this.props.loadData();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SectionContent title="Network Status">
|
||||
<RestFormLoader
|
||||
{...this.props}
|
||||
render={formProps => <NetworkStatusForm {...formProps} />}
|
||||
/>
|
||||
</SectionContent>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default restController(NETWORK_STATUS_ENDPOINT, NetworkStatusController);
|
||||
121
interface/src/network/NetworkStatusForm.tsx
Normal file
121
interface/src/network/NetworkStatusForm.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
|
||||
import { WithTheme, withTheme } from '@material-ui/core/styles';
|
||||
import { Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core';
|
||||
|
||||
import DNSIcon from '@material-ui/icons/Dns';
|
||||
import WifiIcon from '@material-ui/icons/Wifi';
|
||||
import SettingsInputComponentIcon from '@material-ui/icons/SettingsInputComponent';
|
||||
import SettingsInputAntennaIcon from '@material-ui/icons/SettingsInputAntenna';
|
||||
import DeviceHubIcon from '@material-ui/icons/DeviceHub';
|
||||
import RefreshIcon from '@material-ui/icons/Refresh';
|
||||
|
||||
import { RestFormProps, FormActions, FormButton, HighlightAvatar } from '../components';
|
||||
import { networkStatus, networkStatusHighlight, isConnected, isWiFi } from './NetworkStatus';
|
||||
import { NetworkStatus } from './types';
|
||||
|
||||
type NetworkStatusFormProps = RestFormProps<NetworkStatus> & WithTheme;
|
||||
|
||||
class NetworkStatusForm extends Component<NetworkStatusFormProps> {
|
||||
|
||||
dnsServers(status: NetworkStatus) {
|
||||
if (!status.dns_ip_1) {
|
||||
return "none";
|
||||
}
|
||||
return status.dns_ip_1 + (status.dns_ip_2 ? ',' + status.dns_ip_2 : '');
|
||||
}
|
||||
|
||||
createListItems() {
|
||||
const { data, theme } = this.props
|
||||
return (
|
||||
<Fragment>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<HighlightAvatar color={networkStatusHighlight(data, theme)}>
|
||||
<WifiIcon />
|
||||
</HighlightAvatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Status" secondary={networkStatus(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
{
|
||||
isWiFi(data) &&
|
||||
<Fragment>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<SettingsInputAntennaIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="SSID" secondary={data.ssid} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</Fragment>
|
||||
}
|
||||
{ isConnected(data) &&
|
||||
<Fragment>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>IP</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="IP Address" secondary={data.local_ip} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<DeviceHubIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="MAC Address" secondary={data.mac_address} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>#</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Subnet Mask" secondary={data.subnet_mask} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<SettingsInputComponentIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Gateway IP" secondary={data.gateway_ip || "none"} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<DNSIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="DNS Server IP" secondary={this.dnsServers(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</Fragment>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<List>
|
||||
{this.createListItems()}
|
||||
</List>
|
||||
<FormActions>
|
||||
<FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData}>
|
||||
Refresh
|
||||
</FormButton>
|
||||
</FormActions>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withTheme(NetworkStatusForm);
|
||||
168
interface/src/network/WiFiNetworkScanner.tsx
Normal file
168
interface/src/network/WiFiNetworkScanner.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import React, { Component } from 'react';
|
||||
import { withSnackbar, WithSnackbarProps } from 'notistack';
|
||||
|
||||
import { createStyles, WithStyles, Theme, withStyles, Typography, LinearProgress } from '@material-ui/core';
|
||||
import PermScanWifiIcon from '@material-ui/icons/PermScanWifi';
|
||||
|
||||
import { FormActions, FormButton, SectionContent } from '../components';
|
||||
import { redirectingAuthorizedFetch } from '../authentication';
|
||||
import { SCAN_NETWORKS_ENDPOINT, LIST_NETWORKS_ENDPOINT } from '../api';
|
||||
|
||||
import WiFiNetworkSelector from './WiFiNetworkSelector';
|
||||
import { WiFiNetworkList, WiFiNetwork } from './types';
|
||||
|
||||
const NUM_POLLS = 10
|
||||
const POLLING_FREQUENCY = 500
|
||||
const RETRY_EXCEPTION_TYPE = "retry"
|
||||
|
||||
interface WiFiNetworkScannerState {
|
||||
scanningForNetworks: boolean;
|
||||
errorMessage?: string;
|
||||
networkList?: WiFiNetworkList;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
scanningSettings: {
|
||||
margin: theme.spacing(0.5),
|
||||
},
|
||||
scanningSettingsDetails: {
|
||||
margin: theme.spacing(4),
|
||||
textAlign: "center"
|
||||
},
|
||||
scanningProgress: {
|
||||
margin: theme.spacing(4),
|
||||
textAlign: "center"
|
||||
}
|
||||
});
|
||||
|
||||
type WiFiNetworkScannerProps = WithSnackbarProps & WithStyles<typeof styles>;
|
||||
|
||||
class WiFiNetworkScanner extends Component<WiFiNetworkScannerProps, WiFiNetworkScannerState> {
|
||||
|
||||
pollCount: number = 0;
|
||||
|
||||
state: WiFiNetworkScannerState = {
|
||||
scanningForNetworks: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.scanNetworks();
|
||||
}
|
||||
|
||||
requestNetworkScan = () => {
|
||||
const { scanningForNetworks } = this.state;
|
||||
if (!scanningForNetworks) {
|
||||
this.scanNetworks();
|
||||
}
|
||||
}
|
||||
|
||||
scanNetworks() {
|
||||
this.pollCount = 0;
|
||||
this.setState({ scanningForNetworks: true, networkList: undefined, errorMessage: undefined });
|
||||
redirectingAuthorizedFetch(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.props.enqueueSnackbar("Problem scanning: " + error.message, {
|
||||
variant: 'error',
|
||||
});
|
||||
this.setState({ scanningForNetworks: false, networkList: undefined, 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: WiFiNetwork, network2: WiFiNetwork) {
|
||||
if (network1.rssi < network2.rssi)
|
||||
return 1;
|
||||
if (network1.rssi > network2.rssi)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
pollNetworkList = () => {
|
||||
redirectingAuthorizedFetch(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 timely manner.");
|
||||
}
|
||||
}
|
||||
throw Error("Device returned unexpected response code: " + response.status);
|
||||
})
|
||||
.then(json => {
|
||||
json.networks.sort(this.compareNetworks)
|
||||
this.setState({ scanningForNetworks: false, networkList: json, errorMessage: undefined })
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.name !== RETRY_EXCEPTION_TYPE) {
|
||||
this.props.enqueueSnackbar("Problem scanning: " + error.message, {
|
||||
variant: 'error',
|
||||
});
|
||||
this.setState({ scanningForNetworks: false, networkList: undefined, errorMessage: error.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
renderNetworkScanner() {
|
||||
const { classes } = this.props;
|
||||
const { scanningForNetworks, networkList, errorMessage } = this.state;
|
||||
if (scanningForNetworks || !networkList) {
|
||||
return (
|
||||
<div className={classes.scanningSettings}>
|
||||
<LinearProgress className={classes.scanningSettingsDetails} />
|
||||
<Typography variant="h6" className={classes.scanningProgress}>
|
||||
Scanning…
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (errorMessage) {
|
||||
return (
|
||||
<div className={classes.scanningSettings}>
|
||||
<Typography variant="h6" className={classes.scanningSettingsDetails}>
|
||||
{errorMessage}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<WiFiNetworkSelector networkList={networkList} />
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { scanningForNetworks } = this.state;
|
||||
return (
|
||||
<SectionContent title="Network Scanner">
|
||||
{this.renderNetworkScanner()}
|
||||
<FormActions>
|
||||
<FormButton startIcon={<PermScanWifiIcon />} variant="contained" color="secondary" onClick={this.requestNetworkScan} disabled={scanningForNetworks}>
|
||||
Scan again…
|
||||
</FormButton>
|
||||
</FormActions>
|
||||
</SectionContent>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withSnackbar(withStyles(styles)(WiFiNetworkScanner));
|
||||
54
interface/src/network/WiFiNetworkSelector.tsx
Normal file
54
interface/src/network/WiFiNetworkSelector.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Avatar, Badge } from '@material-ui/core';
|
||||
import { List, ListItem, ListItemIcon, ListItemText, ListItemAvatar } from '@material-ui/core';
|
||||
|
||||
import WifiIcon from '@material-ui/icons/Wifi';
|
||||
import LockIcon from '@material-ui/icons/Lock';
|
||||
import LockOpenIcon from '@material-ui/icons/LockOpen';
|
||||
|
||||
import { isNetworkOpen, networkSecurityMode } from './WiFiSecurityModes';
|
||||
import { NetworkConnectionContext } from './NetworkConnectionContext';
|
||||
import { WiFiNetwork, WiFiNetworkList } from './types';
|
||||
|
||||
interface WiFiNetworkSelectorProps {
|
||||
networkList: WiFiNetworkList;
|
||||
}
|
||||
|
||||
class WiFiNetworkSelector extends Component<WiFiNetworkSelectorProps> {
|
||||
|
||||
static contextType = NetworkConnectionContext;
|
||||
context!: React.ContextType<typeof NetworkConnectionContext>;
|
||||
|
||||
renderNetwork = (network: WiFiNetwork) => {
|
||||
return (
|
||||
<ListItem key={network.bssid} button onClick={() => this.context.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() {
|
||||
return (
|
||||
<List>
|
||||
{this.props.networkList.networks.map(this.renderNetwork)}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default WiFiNetworkSelector;
|
||||
21
interface/src/network/WiFiSecurityModes.ts
Normal file
21
interface/src/network/WiFiSecurityModes.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { WiFiNetwork, WiFiEncryptionType } from "./types";
|
||||
|
||||
export const isNetworkOpen = ({ encryption_type }: WiFiNetwork) => encryption_type === WiFiEncryptionType.WIFI_AUTH_OPEN;
|
||||
|
||||
export const networkSecurityMode = ({ encryption_type }: WiFiNetwork) => {
|
||||
switch (encryption_type) {
|
||||
case WiFiEncryptionType.WIFI_AUTH_WEP:
|
||||
case WiFiEncryptionType.WIFI_AUTH_WEP_PSK:
|
||||
return "WEP";
|
||||
case WiFiEncryptionType.WIFI_AUTH_WEP2_PSK:
|
||||
return "WEP2";
|
||||
case WiFiEncryptionType.WIFI_AUTH_WPA_WPA2_PSK:
|
||||
return "WPA/WEP2";
|
||||
case WiFiEncryptionType.WIFI_AUTH_WPA2_ENTERPRISE:
|
||||
return "WEP2 Enterprise";
|
||||
case WiFiEncryptionType.WIFI_AUTH_OPEN:
|
||||
return "None";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
58
interface/src/network/types.ts
Normal file
58
interface/src/network/types.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
export enum NetworkConnectionStatus {
|
||||
WIFI_STATUS_IDLE = 0,
|
||||
WIFI_STATUS_NO_SSID_AVAIL = 1,
|
||||
WIFI_STATUS_CONNECTED = 3,
|
||||
WIFI_STATUS_CONNECT_FAILED = 4,
|
||||
WIFI_STATUS_CONNECTION_LOST = 5,
|
||||
WIFI_STATUS_DISCONNECTED = 6,
|
||||
ETHERNET_STATUS_CONNECTED = 10,
|
||||
WIFI_STATUS_NO_SHIELD = 255
|
||||
}
|
||||
|
||||
export enum WiFiEncryptionType {
|
||||
WIFI_AUTH_OPEN = 0,
|
||||
WIFI_AUTH_WEP = 1,
|
||||
WIFI_AUTH_WEP_PSK = 2,
|
||||
WIFI_AUTH_WEP2_PSK = 3,
|
||||
WIFI_AUTH_WPA_WPA2_PSK = 4,
|
||||
WIFI_AUTH_WPA2_ENTERPRISE = 5
|
||||
}
|
||||
|
||||
export interface NetworkStatus {
|
||||
status: NetworkConnectionStatus;
|
||||
local_ip: string;
|
||||
mac_address: string;
|
||||
rssi: number;
|
||||
ssid: string;
|
||||
bssid: string;
|
||||
channel: number;
|
||||
subnet_mask: string;
|
||||
gateway_ip: string;
|
||||
dns_ip_1: string;
|
||||
dns_ip_2: string;
|
||||
}
|
||||
|
||||
export interface NetworkSettings {
|
||||
ssid: string;
|
||||
password: string;
|
||||
hostname: string;
|
||||
ethernet_profile: number;
|
||||
static_ip_config: boolean;
|
||||
local_ip?: string;
|
||||
gateway_ip?: string;
|
||||
subnet_mask?: string;
|
||||
dns_ip_1?: string;
|
||||
dns_ip_2?: string;
|
||||
}
|
||||
|
||||
export interface WiFiNetworkList {
|
||||
networks: WiFiNetwork[];
|
||||
}
|
||||
|
||||
export interface WiFiNetwork {
|
||||
rssi: number;
|
||||
ssid: string;
|
||||
bssid: string;
|
||||
channel: number;
|
||||
encryption_type: WiFiEncryptionType;
|
||||
}
|
||||
Reference in New Issue
Block a user