commit
67eb9d4017
91
README.md
91
README.md
@ -42,8 +42,9 @@ Resource | Description
|
|||||||
---- | -----------
|
---- | -----------
|
||||||
[data/](data) | The file system image directory
|
[data/](data) | The file system image directory
|
||||||
[interface/](interface) | React based front end
|
[interface/](interface) | React based front end
|
||||||
[src/](src) | C++ back end for the ESP8266 device
|
[src/](src) | The main.cpp and demo project to get you started
|
||||||
[platformio.ini](platformio.ini) | PlatformIO project configuration file
|
[platformio.ini](platformio.ini) | PlatformIO project configuration file
|
||||||
|
[lib/framework/](lib/framework) | C++ back end for the ESP8266 device
|
||||||
|
|
||||||
### Building the firmware
|
### Building the firmware
|
||||||
|
|
||||||
@ -247,13 +248,92 @@ There is also a manifest file which contains the app name to use when adding the
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Back End Overview
|
## Back end overview
|
||||||
|
|
||||||
The back end is a set of REST endpoints hosted by a [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) instance. The source is split up by feature, for example [WiFiScanner.h](src/WiFiScanner.h) implements the end points for scanning for available networks.
|
The back end is a set of REST endpoints hosted by a [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) instance. The ['lib/framework'](lib/framework) directory contains the majority of the back end code. The framework contains of a number of useful utility classes which you can use when extending it. The project also comes with a demo project to give you some help getting started.
|
||||||
|
|
||||||
There is an abstract class [SettingsService.h](src/SettingsService.h) that provides an easy means of adding configurable services/features to the device. It takes care of writing the settings as JSON to SPIFFS. All you need to do is extend the class with your required configuration and implement the functions which serialize the settings to/from JSON. JSON serialization utilizes the excellent [ArduinoJson](https://github.com/bblanchon/ArduinoJson) library.
|
The framework's source is split up by feature, for example [WiFiScanner.h](lib/framework/WiFiScanner.h) implements the end points for scanning for available networks where as [WiFiSettingsService.h](lib/framework/WiFiSettingsService.h) handles configuring the WiFi settings and managing the WiFi connection.
|
||||||
|
|
||||||
Here is a example of a service with username and password settings:
|
### Initializing the framework
|
||||||
|
|
||||||
|
The ['src/main.cpp'](src/main.cpp) file constructs the webserver and initializes the framework. You can add endpoints to the server here to support your IoT project. The main loop is also accessable so you can run your own code easily.
|
||||||
|
|
||||||
|
The following code creates the web server, esp8266React framework and the demo project instance:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
AsyncWebServer server(80);
|
||||||
|
ESP8266React esp8266React(&server, &SPIFFS);
|
||||||
|
DemoProject demoProject = DemoProject(&server, &SPIFFS, esp8266React.getSecurityManager());
|
||||||
|
```
|
||||||
|
|
||||||
|
Now in the `setup()` function the initialization is performed:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void setup() {
|
||||||
|
// start serial and filesystem
|
||||||
|
Serial.begin(SERIAL_BAUD_RATE);
|
||||||
|
|
||||||
|
// start the file system (must be done before starting the framework)
|
||||||
|
SPIFFS.begin();
|
||||||
|
|
||||||
|
// start the framework and demo project
|
||||||
|
esp8266React.begin();
|
||||||
|
|
||||||
|
// start the demo project
|
||||||
|
demoProject.begin();
|
||||||
|
|
||||||
|
// start the server
|
||||||
|
server.begin();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally the loop calls the framework's loop function to service the frameworks features. You can add your own code in here, as shown with the demo project:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void loop() {
|
||||||
|
// run the framework's loop function
|
||||||
|
esp8266React.loop();
|
||||||
|
|
||||||
|
// run the demo project's loop function
|
||||||
|
demoProject.loop();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding endpoints
|
||||||
|
|
||||||
|
There are some simple classes that support adding configurable services/features to the device:
|
||||||
|
|
||||||
|
Class | Description
|
||||||
|
----- | -----------
|
||||||
|
[SimpleService.h](lib/framework/SimpleService.h) | Exposes an endpoint to read and write settings as JSON. Extend this class and implement the functions which serialize the settings to/from JSON.
|
||||||
|
[SettingsService.h](lib/framework/SettingsService.h) | As above, however this class also handles persisting the settings as JSON to the file system.
|
||||||
|
[AdminSettingsService.h](lib/framework/AdminSettingsService.h) | Extends SettingsService to secure the endpoint to administrators only, the authentication predicate can be overridden if required.
|
||||||
|
|
||||||
|
The demo project shows how these can be used, explore the framework classes for more examples.
|
||||||
|
|
||||||
|
### Security features
|
||||||
|
|
||||||
|
The framework has security features to prevent unauthorized use of the device. This is driven by [SecurityManager.h](lib/framework/SecurityManager.h).
|
||||||
|
|
||||||
|
On successful authentication, the /rest/signIn endpoint issues a JWT which is then sent using Bearer Authentication. The framework come with built in predicates for verifying a users access level. The built in AuthenticationPredicates can be found in [SecurityManager.h](lib/framework/SecurityManager.h):
|
||||||
|
|
||||||
|
Predicate | Description
|
||||||
|
-------------------- | -----------
|
||||||
|
NONE_REQUIRED | No authentication is required.
|
||||||
|
IS_AUTHENTICATED | Any authenticated principal is permitted.
|
||||||
|
IS_ADMIN | The authenticated principal must be an admin.
|
||||||
|
|
||||||
|
You can use the security manager to wrap any web handler with an authentication predicate:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
server->on("/rest/someService", HTTP_GET,
|
||||||
|
_securityManager->wrapRequest(std::bind(&SomeService::someService, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively you can extend [AdminSettingsService.h](lib/framework/AdminSettingsService.h) and optionally override `getAuthenticationPredicate()` to secure an endpoint.
|
||||||
|
|
||||||
|
## Extending the framework
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include <SettingsService.h>
|
#include <SettingsService.h>
|
||||||
@ -321,6 +401,7 @@ void reconfigureTheService() {
|
|||||||
|
|
||||||
* [React](https://reactjs.org/)
|
* [React](https://reactjs.org/)
|
||||||
* [Material-UI](https://material-ui-next.com/)
|
* [Material-UI](https://material-ui-next.com/)
|
||||||
|
* [notistack](https://github.com/iamhosseindhv/notistack)
|
||||||
* [Time](https://github.com/PaulStoffregen/Time)
|
* [Time](https://github.com/PaulStoffregen/Time)
|
||||||
* [NtpClient](https://github.com/gmag11/NtpClient)
|
* [NtpClient](https://github.com/gmag11/NtpClient)
|
||||||
* [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
|
* [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"provision_mode": 0,
|
"provision_mode": 0,
|
||||||
"ssid": "ESP8266-React",
|
"ssid": "ESP8266-React",
|
||||||
"password": "esp-react"
|
"password": "esp-react"
|
||||||
}
|
}
|
3
data/config/demoSettings.json
Normal file
3
data/config/demoSettings.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"blink_speed": 100
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"server":"pool.ntp.org",
|
"server": "pool.ntp.org",
|
||||||
"interval":3600
|
"interval": 3600
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"enabled":true,
|
"enabled": true,
|
||||||
"port": 8266,
|
"port": 8266,
|
||||||
"password": "esp-react"
|
"password": "esp-react"
|
||||||
}
|
}
|
@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"jwt_secret":"esp8266-react",
|
"jwt_secret": "esp8266-react",
|
||||||
"users": [
|
"users": [
|
||||||
{
|
{
|
||||||
"username": "admin",
|
"username": "admin",
|
||||||
"password": "admin",
|
"password": "admin",
|
||||||
"admin": true
|
"admin": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"username": "guest",
|
"username": "guest",
|
||||||
"password": "guest",
|
"password": "guest",
|
||||||
"admin": false
|
"admin": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ssid":"",
|
"ssid": "",
|
||||||
"password":"password",
|
"password": "password",
|
||||||
"hostname":"esp8266-react",
|
"hostname": "esp8266-react",
|
||||||
"static_ip_config":false
|
"static_ip_config": false
|
||||||
}
|
}
|
@ -1 +1,5 @@
|
|||||||
REACT_APP_NAME=ESP8266 React
|
# This is the name of your project. It appears on the sign-in page and in the menu bar.
|
||||||
|
REACT_APP_PROJECT_NAME=ESP8266 React
|
||||||
|
|
||||||
|
# This is the url path your project will be exposed under.
|
||||||
|
REACT_APP_PROJECT_PATH=project
|
||||||
|
@ -1 +1,3 @@
|
|||||||
REACT_APP_ENDPOINT_ROOT=http://192.168.0.11/rest/
|
# Change the IP address to that of your ESP device to enable local development of the UI.
|
||||||
|
# Remember to also enable CORS in platformio.ini before uploading the code to the device.
|
||||||
|
REACT_APP_ENDPOINT_ROOT=http://192.168.0.20/rest/
|
||||||
|
2516
interface/package-lock.json
generated
2516
interface/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -3,20 +3,20 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-ui/core": "^4.3.1",
|
"@material-ui/core": "^4.4.3",
|
||||||
"@material-ui/icons": "^4.2.1",
|
"@material-ui/icons": "^4.4.3",
|
||||||
"compression-webpack-plugin": "^2.0.0",
|
"compression-webpack-plugin": "^2.0.0",
|
||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"notistack": "^0.8.9",
|
"notistack": "^0.8.9",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^16.8.6",
|
"react": "^16.10.1",
|
||||||
"react-dom": "^16.8.6",
|
"react-dom": "^16.10.1",
|
||||||
"react-form-validator-core": "^0.6.3",
|
"react-form-validator-core": "^0.6.4",
|
||||||
"react-jss": "^10.0.0-alpha.23",
|
"react-jss": "^10.0.0",
|
||||||
"react-material-ui-form-validator": "^2.0.9",
|
"react-material-ui-form-validator": "^2.0.9",
|
||||||
"react-router": "^5.0.1",
|
"react-router": "^5.1.1",
|
||||||
"react-router-dom": "^5.0.1",
|
"react-router-dom": "^5.1.1",
|
||||||
"react-scripts": "3.0.1"
|
"react-scripts": "3.0.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -2,18 +2,18 @@ import React, { Component } from 'react';
|
|||||||
|
|
||||||
import { Redirect, Switch } from 'react-router';
|
import { Redirect, Switch } from 'react-router';
|
||||||
|
|
||||||
|
import { PROJECT_PATH } from './constants/Env';
|
||||||
import * as Authentication from './authentication/Authentication';
|
import * as Authentication from './authentication/Authentication';
|
||||||
import AuthenticationWrapper from './authentication/AuthenticationWrapper';
|
import AuthenticationWrapper from './authentication/AuthenticationWrapper';
|
||||||
import AuthenticatedRoute from './authentication/AuthenticatedRoute';
|
import AuthenticatedRoute from './authentication/AuthenticatedRoute';
|
||||||
import UnauthenticatedRoute from './authentication/UnauthenticatedRoute';
|
import UnauthenticatedRoute from './authentication/UnauthenticatedRoute';
|
||||||
|
|
||||||
import SignInPage from './containers/SignInPage';
|
import SignInPage from './containers/SignInPage';
|
||||||
|
|
||||||
import WiFiConnection from './sections/WiFiConnection';
|
import WiFiConnection from './sections/WiFiConnection';
|
||||||
import AccessPoint from './sections/AccessPoint';
|
import AccessPoint from './sections/AccessPoint';
|
||||||
import NetworkTime from './sections/NetworkTime';
|
import NetworkTime from './sections/NetworkTime';
|
||||||
import Security from './sections/Security';
|
import Security from './sections/Security';
|
||||||
import System from './sections/System';
|
import System from './sections/System';
|
||||||
|
import ProjectRouting from './project/ProjectRouting';
|
||||||
|
|
||||||
class AppRouting extends Component {
|
class AppRouting extends Component {
|
||||||
|
|
||||||
@ -31,6 +31,7 @@ class AppRouting extends Component {
|
|||||||
<AuthenticatedRoute exact path="/ntp/*" component={NetworkTime} />
|
<AuthenticatedRoute exact path="/ntp/*" component={NetworkTime} />
|
||||||
<AuthenticatedRoute exact path="/security/*" component={Security} />
|
<AuthenticatedRoute exact path="/security/*" component={Security} />
|
||||||
<AuthenticatedRoute exact path="/system/*" component={System} />
|
<AuthenticatedRoute exact path="/system/*" component={System} />
|
||||||
|
<AuthenticatedRoute exact path={`/${PROJECT_PATH}/*`} component={ProjectRouting} />
|
||||||
<Redirect to="/" />
|
<Redirect to="/" />
|
||||||
</Switch>
|
</Switch>
|
||||||
</AuthenticationWrapper>
|
</AuthenticationWrapper>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import history from '../history';
|
import history from '../history';
|
||||||
|
import { PROJECT_PATH } from '../constants/Env';
|
||||||
|
|
||||||
export const ACCESS_TOKEN = 'access_token';
|
export const ACCESS_TOKEN = 'access_token';
|
||||||
export const LOGIN_PATHNAME = 'loginPathname';
|
export const LOGIN_PATHNAME = 'loginPathname';
|
||||||
@ -21,7 +22,7 @@ export function fetchLoginRedirect() {
|
|||||||
const loginSearch = localStorage.getItem(LOGIN_SEARCH);
|
const loginSearch = localStorage.getItem(LOGIN_SEARCH);
|
||||||
clearLoginRedirect();
|
clearLoginRedirect();
|
||||||
return {
|
return {
|
||||||
pathname: loginPathname || "/wifi/",
|
pathname: loginPathname || `/${PROJECT_PATH}/`,
|
||||||
search: (loginPathname && loginSearch) || undefined
|
search: (loginPathname && loginSearch) || undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
58
interface/src/components/LoadingNotification.js
Normal file
58
interface/src/components/LoadingNotification.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
import LinearProgress from '@material-ui/core/LinearProgress';
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
|
||||||
|
const useStyles = makeStyles(theme => ({
|
||||||
|
loadingSettings: {
|
||||||
|
margin: theme.spacing(0.5),
|
||||||
|
},
|
||||||
|
loadingSettingsDetails: {
|
||||||
|
margin: theme.spacing(4),
|
||||||
|
textAlign: "center"
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
marginRight: theme.spacing(2),
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default function LoadingNotification(props) {
|
||||||
|
const classes = useStyles();
|
||||||
|
const { fetched, errorMessage, onReset, render } = props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
fetched ?
|
||||||
|
errorMessage ?
|
||||||
|
<div className={classes.loadingSettings}>
|
||||||
|
<Typography variant="h6" className={classes.loadingSettingsDetails}>
|
||||||
|
{errorMessage}
|
||||||
|
</Typography>
|
||||||
|
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
render()
|
||||||
|
:
|
||||||
|
<div className={classes.loadingSettings}>
|
||||||
|
<LinearProgress className={classes.loadingSettingsDetails} />
|
||||||
|
<Typography variant="h6" className={classes.loadingSettingsDetails}>
|
||||||
|
Loading...
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadingNotification.propTypes = {
|
||||||
|
fetched: PropTypes.bool.isRequired,
|
||||||
|
onReset: PropTypes.func.isRequired,
|
||||||
|
errorMessage: PropTypes.string,
|
||||||
|
render: PropTypes.func.isRequired
|
||||||
|
};
|
@ -30,7 +30,8 @@ import CardContent from '@material-ui/core/CardContent';
|
|||||||
import CardActions from '@material-ui/core/CardActions';
|
import CardActions from '@material-ui/core/CardActions';
|
||||||
import Avatar from '@material-ui/core/Avatar';
|
import Avatar from '@material-ui/core/Avatar';
|
||||||
|
|
||||||
import { APP_NAME } from '../constants/App';
|
import ProjectMenu from '../project/ProjectMenu';
|
||||||
|
import { PROJECT_NAME } from '../constants/Env';
|
||||||
import { withAuthenticationContext } from '../authentication/Context.js';
|
import { withAuthenticationContext } from '../authentication/Context.js';
|
||||||
|
|
||||||
const drawerWidth = 290;
|
const drawerWidth = 290;
|
||||||
@ -65,8 +66,7 @@ const styles = theme => ({
|
|||||||
width: drawerWidth,
|
width: drawerWidth,
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
flexGrow: 1,
|
flexGrow: 1
|
||||||
padding: theme.spacing(),
|
|
||||||
},
|
},
|
||||||
authMenu: {
|
authMenu: {
|
||||||
zIndex: theme.zIndex.tooltip,
|
zIndex: theme.zIndex.tooltip,
|
||||||
@ -112,11 +112,13 @@ class MenuAppBar extends React.Component {
|
|||||||
<div>
|
<div>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<Typography variant="h6" color="primary">
|
<Typography variant="h6" color="primary">
|
||||||
{APP_NAME}
|
{PROJECT_NAME}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Divider absolute />
|
<Divider absolute />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<ProjectMenu />
|
||||||
|
<Divider />
|
||||||
<List>
|
<List>
|
||||||
<ListItem to='/wifi/' selected={path.startsWith('/wifi/')} button component={Link}>
|
<ListItem to='/wifi/' selected={path.startsWith('/wifi/')} button component={Link}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
@ -195,7 +197,7 @@ class MenuAppBar extends React.Component {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
<Divider />
|
<Divider />
|
||||||
<CardActions className={classes.authMenuActions}>
|
<CardActions className={classes.authMenuActions}>
|
||||||
<Button className={classes.authMenuButtons} variant="contained" color="primary" onClick={authenticationContext.signOut}>Sign Out</Button>
|
<Button variant="contained" color="primary" onClick={authenticationContext.signOut}>Sign Out</Button>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</Card>
|
</Card>
|
||||||
</ClickAwayListener>
|
</ClickAwayListener>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { withSnackbar } from 'notistack';
|
import { withSnackbar } from 'notistack';
|
||||||
import { redirectingAuthorizedFetch } from '../authentication/Authentication';
|
import { redirectingAuthorizedFetch } from '../authentication/Authentication';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* It is unlikely this application will grow complex enough to require redux.
|
* It is unlikely this application will grow complex enough to require redux.
|
||||||
*
|
*
|
||||||
@ -51,10 +52,11 @@ export const restComponent = (endpointUrl, FormComponent) => {
|
|||||||
})
|
})
|
||||||
.then(json => { this.setState({ data: json, fetched: true }) })
|
.then(json => { this.setState({ data: json, fetched: true }) })
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.props.enqueueSnackbar("Problem fetching: " + error.message, {
|
const errorMessage = error.message || "Unknown error";
|
||||||
|
this.props.enqueueSnackbar("Problem fetching: " + errorMessage, {
|
||||||
variant: 'error',
|
variant: 'error',
|
||||||
});
|
});
|
||||||
this.setState({ data: null, fetched: true, errorMessage: error.message });
|
this.setState({ data: null, fetched: true, errorMessage });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,19 +81,26 @@ export const restComponent = (endpointUrl, FormComponent) => {
|
|||||||
});
|
});
|
||||||
this.setState({ data: json, fetched: true });
|
this.setState({ data: json, fetched: true });
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.props.enqueueSnackbar("Problem saving: " + error.message, {
|
const errorMessage = error.message || "Unknown error";
|
||||||
|
this.props.enqueueSnackbar("Problem saving: " + errorMessage, {
|
||||||
variant: 'error',
|
variant: 'error',
|
||||||
});
|
});
|
||||||
this.setState({ data: null, fetched: true, errorMessage: error.message });
|
this.setState({ data: null, fetched: true, errorMessage });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleValueChange = name => event => {
|
handleValueChange = name => (event) => {
|
||||||
const { data } = this.state;
|
const { data } = this.state;
|
||||||
data[name] = event.target.value;
|
data[name] = event.target.value;
|
||||||
this.setState({ data });
|
this.setState({ data });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleSliderChange = name => (event, newValue) => {
|
||||||
|
const { data } = this.state;
|
||||||
|
data[name] = newValue;
|
||||||
|
this.setState({ data });
|
||||||
|
};
|
||||||
|
|
||||||
handleCheckboxChange = name => event => {
|
handleCheckboxChange = name => event => {
|
||||||
const { data } = this.state;
|
const { data } = this.state;
|
||||||
data[name] = event.target.checked;
|
data[name] = event.target.checked;
|
||||||
@ -102,6 +111,7 @@ export const restComponent = (endpointUrl, FormComponent) => {
|
|||||||
return <FormComponent
|
return <FormComponent
|
||||||
handleValueChange={this.handleValueChange}
|
handleValueChange={this.handleValueChange}
|
||||||
handleCheckboxChange={this.handleCheckboxChange}
|
handleCheckboxChange={this.handleCheckboxChange}
|
||||||
|
handleSliderChange={this.handleSliderChange}
|
||||||
setData={this.setData}
|
setData={this.setData}
|
||||||
saveData={this.saveData}
|
saveData={this.saveData}
|
||||||
loadData={this.loadData}
|
loadData={this.loadData}
|
||||||
|
@ -8,15 +8,15 @@ import Typography from '@material-ui/core/Typography';
|
|||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
content: {
|
content: {
|
||||||
padding: theme.spacing(2),
|
padding: theme.spacing(2),
|
||||||
margin: theme.spacing(2),
|
margin: theme.spacing(3),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function SectionContent(props) {
|
function SectionContent(props) {
|
||||||
const { children, classes, title } = props;
|
const { children, classes, title, titleGutter } = props;
|
||||||
return (
|
return (
|
||||||
<Paper className={classes.content}>
|
<Paper className={classes.content}>
|
||||||
<Typography variant="h6">
|
<Typography variant="h6" gutterBottom={titleGutter}>
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
{children}
|
{children}
|
||||||
@ -30,7 +30,8 @@ SectionContent.propTypes = {
|
|||||||
PropTypes.arrayOf(PropTypes.node),
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
PropTypes.node
|
PropTypes.node
|
||||||
]).isRequired,
|
]).isRequired,
|
||||||
title: PropTypes.string.isRequired
|
title: PropTypes.string.isRequired,
|
||||||
|
titleGutter: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withStyles(styles)(SectionContent);
|
export default withStyles(styles)(SectionContent);
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export const APP_NAME = process.env.REACT_APP_NAME;
|
|
@ -1,4 +1,4 @@
|
|||||||
const ENDPOINT_ROOT = process.env.REACT_APP_ENDPOINT_ROOT;
|
import { ENDPOINT_ROOT } from '../constants/Env';
|
||||||
|
|
||||||
export const NTP_STATUS_ENDPOINT = ENDPOINT_ROOT + "ntpStatus";
|
export const NTP_STATUS_ENDPOINT = ENDPOINT_ROOT + "ntpStatus";
|
||||||
export const NTP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "ntpSettings";
|
export const NTP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "ntpSettings";
|
||||||
|
3
interface/src/constants/Env.js
Normal file
3
interface/src/constants/Env.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const PROJECT_NAME = process.env.REACT_APP_PROJECT_NAME;
|
||||||
|
export const PROJECT_PATH = process.env.REACT_APP_PROJECT_PATH;
|
||||||
|
export const ENDPOINT_ROOT = process.env.REACT_APP_ENDPOINT_ROOT;
|
@ -1,7 +1,8 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import { AP_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
import { AP_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
||||||
import { restComponent } from '../components/RestComponent';
|
import { restComponent } from '../components/RestComponent';
|
||||||
|
import LoadingNotification from '../components/LoadingNotification';
|
||||||
import SectionContent from '../components/SectionContent';
|
import SectionContent from '../components/SectionContent';
|
||||||
import APSettingsForm from '../forms/APSettingsForm';
|
import APSettingsForm from '../forms/APSettingsForm';
|
||||||
|
|
||||||
@ -12,16 +13,21 @@ class APSettings extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { data, fetched, errorMessage } = this.props;
|
const { fetched, errorMessage, data, saveData, loadData, handleValueChange } = this.props;
|
||||||
return (
|
return (
|
||||||
<SectionContent title="AP Settings">
|
<SectionContent title="AP Settings">
|
||||||
<APSettingsForm
|
<LoadingNotification
|
||||||
apSettings={data}
|
onReset={loadData}
|
||||||
apSettingsFetched={fetched}
|
fetched={fetched}
|
||||||
errorMessage={errorMessage}
|
errorMessage={errorMessage}
|
||||||
onSubmit={this.props.saveData}
|
render={() =>
|
||||||
onReset={this.props.loadData}
|
<APSettingsForm
|
||||||
handleValueChange={this.props.handleValueChange}
|
apSettings={data}
|
||||||
|
onSubmit={saveData}
|
||||||
|
onReset={loadData}
|
||||||
|
handleValueChange={handleValueChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
)
|
)
|
||||||
|
@ -2,8 +2,6 @@ import React, { Component, Fragment } from 'react';
|
|||||||
|
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import LinearProgress from '@material-ui/core/LinearProgress';
|
|
||||||
import Typography from '@material-ui/core/Typography';
|
|
||||||
import List from '@material-ui/core/List';
|
import List from '@material-ui/core/List';
|
||||||
import ListItem from '@material-ui/core/ListItem';
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
import ListItemText from '@material-ui/core/ListItemText';
|
import ListItemText from '@material-ui/core/ListItemText';
|
||||||
@ -15,6 +13,7 @@ import DeviceHubIcon from '@material-ui/icons/DeviceHub';
|
|||||||
import ComputerIcon from '@material-ui/icons/Computer';
|
import ComputerIcon from '@material-ui/icons/Computer';
|
||||||
|
|
||||||
import { restComponent } from '../components/RestComponent';
|
import { restComponent } from '../components/RestComponent';
|
||||||
|
import LoadingNotification from '../components/LoadingNotification';
|
||||||
import SectionContent from '../components/SectionContent'
|
import SectionContent from '../components/SectionContent'
|
||||||
|
|
||||||
import * as Highlight from '../constants/Highlight';
|
import * as Highlight from '../constants/Highlight';
|
||||||
@ -27,10 +26,6 @@ const styles = theme => ({
|
|||||||
["apStatus_" + Highlight.IDLE]: {
|
["apStatus_" + Highlight.IDLE]: {
|
||||||
backgroundColor: theme.palette.highlight_idle
|
backgroundColor: theme.palette.highlight_idle
|
||||||
},
|
},
|
||||||
fetching: {
|
|
||||||
margin: theme.spacing(4),
|
|
||||||
textAlign: "center"
|
|
||||||
},
|
|
||||||
button: {
|
button: {
|
||||||
marginRight: theme.spacing(2),
|
marginRight: theme.spacing(2),
|
||||||
marginTop: theme.spacing(2),
|
marginTop: theme.spacing(2),
|
||||||
@ -96,9 +91,7 @@ class APStatus extends Component {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<List>
|
<List>
|
||||||
<Fragment>
|
{this.createListItems(data, classes)}
|
||||||
{this.createListItems(data, classes)}
|
|
||||||
</Fragment>
|
|
||||||
</List>
|
</List>
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={this.props.loadData}>
|
<Button variant="contained" color="secondary" className={classes.button} onClick={this.props.loadData}>
|
||||||
Refresh
|
Refresh
|
||||||
@ -108,30 +101,17 @@ class APStatus extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { data, fetched, errorMessage, classes } = this.props;
|
const { fetched, errorMessage, data, loadData, classes } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SectionContent title="AP Status">
|
<SectionContent title="AP Status">
|
||||||
{
|
<LoadingNotification
|
||||||
!fetched ?
|
onReset={loadData}
|
||||||
<div>
|
fetched={fetched}
|
||||||
<LinearProgress className={classes.fetching} />
|
errorMessage={errorMessage}
|
||||||
<Typography variant="h4" className={classes.fetching}>
|
render={
|
||||||
Loading...
|
() => this.renderAPStatus(data, classes)
|
||||||
</Typography>
|
}
|
||||||
</div>
|
/>
|
||||||
:
|
|
||||||
data ? this.renderAPStatus(data, classes)
|
|
||||||
:
|
|
||||||
<div>
|
|
||||||
<Typography variant="h4" className={classes.fetching}>
|
|
||||||
{errorMessage}
|
|
||||||
</Typography>
|
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={this.props.loadData}>
|
|
||||||
Refresh
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,9 @@ import React, { Component } from 'react';
|
|||||||
|
|
||||||
import { SECURITY_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
import { SECURITY_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
||||||
import { restComponent } from '../components/RestComponent';
|
import { restComponent } from '../components/RestComponent';
|
||||||
import ManageUsersForm from '../forms/ManageUsersForm';
|
import LoadingNotification from '../components/LoadingNotification';
|
||||||
import SectionContent from '../components/SectionContent';
|
import SectionContent from '../components/SectionContent';
|
||||||
|
import ManageUsersForm from '../forms/ManageUsersForm';
|
||||||
|
|
||||||
class ManageUsers extends Component {
|
class ManageUsers extends Component {
|
||||||
|
|
||||||
@ -12,17 +13,22 @@ class ManageUsers extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { data, fetched, errorMessage } = this.props;
|
const { fetched, errorMessage, data, saveData, loadData, setData, handleValueChange } = this.props;
|
||||||
return (
|
return (
|
||||||
<SectionContent title="Manage Users">
|
<SectionContent title="Manage Users" titleGutter>
|
||||||
<ManageUsersForm
|
<LoadingNotification
|
||||||
userData={data}
|
onReset={loadData}
|
||||||
userDataFetched={fetched}
|
fetched={fetched}
|
||||||
errorMessage={errorMessage}
|
errorMessage={errorMessage}
|
||||||
onSubmit={this.props.saveData}
|
render={() =>
|
||||||
onReset={this.props.loadData}
|
<ManageUsersForm
|
||||||
setData={this.props.setData}
|
userData={data}
|
||||||
handleValueChange={this.props.handleValueChange}
|
onSubmit={saveData}
|
||||||
|
onReset={loadData}
|
||||||
|
setData={setData}
|
||||||
|
handleValueChange={handleValueChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
)
|
)
|
||||||
|
@ -1,27 +1,33 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import { NTP_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
import { NTP_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
||||||
import {restComponent} from '../components/RestComponent';
|
import { restComponent } from '../components/RestComponent';
|
||||||
|
import LoadingNotification from '../components/LoadingNotification';
|
||||||
import SectionContent from '../components/SectionContent';
|
import SectionContent from '../components/SectionContent';
|
||||||
import NTPSettingsForm from '../forms/NTPSettingsForm';
|
import NTPSettingsForm from '../forms/NTPSettingsForm';
|
||||||
|
|
||||||
class NTPSettings extends Component {
|
class NTPSettings extends Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.loadData();
|
this.props.loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { data, fetched, errorMessage } = this.props;
|
const { fetched, errorMessage, data, saveData, loadData, handleValueChange } = this.props;
|
||||||
return (
|
return (
|
||||||
<SectionContent title="NTP Settings">
|
<SectionContent title="NTP Settings">
|
||||||
<NTPSettingsForm
|
<LoadingNotification
|
||||||
ntpSettings={data}
|
onReset={loadData}
|
||||||
ntpSettingsFetched={fetched}
|
fetched={fetched}
|
||||||
errorMessage={errorMessage}
|
errorMessage={errorMessage}
|
||||||
onSubmit={this.props.saveData}
|
render={() =>
|
||||||
onReset={this.props.loadData}
|
<NTPSettingsForm
|
||||||
handleValueChange={this.props.handleValueChange}
|
ntpSettings={data}
|
||||||
|
onSubmit={saveData}
|
||||||
|
onReset={loadData}
|
||||||
|
handleValueChange={handleValueChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
)
|
)
|
||||||
|
@ -2,8 +2,6 @@ import React, { Component, Fragment } from 'react';
|
|||||||
|
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import LinearProgress from '@material-ui/core/LinearProgress';
|
|
||||||
import Typography from '@material-ui/core/Typography';
|
|
||||||
import List from '@material-ui/core/List';
|
import List from '@material-ui/core/List';
|
||||||
import ListItem from '@material-ui/core/ListItem';
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
||||||
@ -22,6 +20,7 @@ import * as Highlight from '../constants/Highlight';
|
|||||||
import { unixTimeToTimeAndDate } from '../constants/TimeFormat';
|
import { unixTimeToTimeAndDate } from '../constants/TimeFormat';
|
||||||
import { NTP_STATUS_ENDPOINT } from '../constants/Endpoints';
|
import { NTP_STATUS_ENDPOINT } from '../constants/Endpoints';
|
||||||
import { restComponent } from '../components/RestComponent';
|
import { restComponent } from '../components/RestComponent';
|
||||||
|
import LoadingNotification from '../components/LoadingNotification';
|
||||||
import SectionContent from '../components/SectionContent';
|
import SectionContent from '../components/SectionContent';
|
||||||
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
@ -36,10 +35,6 @@ const styles = theme => ({
|
|||||||
["ntpStatus_" + Highlight.WARN]: {
|
["ntpStatus_" + Highlight.WARN]: {
|
||||||
backgroundColor: theme.palette.highlight_warn
|
backgroundColor: theme.palette.highlight_warn
|
||||||
},
|
},
|
||||||
fetching: {
|
|
||||||
margin: theme.spacing(4),
|
|
||||||
textAlign: "center"
|
|
||||||
},
|
|
||||||
button: {
|
button: {
|
||||||
marginRight: theme.spacing(2),
|
marginRight: theme.spacing(2),
|
||||||
marginTop: theme.spacing(2),
|
marginTop: theme.spacing(2),
|
||||||
@ -131,32 +126,19 @@ class NTPStatus extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { data, fetched, errorMessage, classes } = this.props;
|
const { data, fetched, errorMessage, loadData, classes } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SectionContent title="NTP Status">
|
<SectionContent title="NTP Status">
|
||||||
{
|
<LoadingNotification
|
||||||
!fetched ?
|
onReset={loadData}
|
||||||
<div>
|
fetched={fetched}
|
||||||
<LinearProgress className={classes.fetching} />
|
errorMessage={errorMessage}
|
||||||
<Typography variant="h4" className={classes.fetching}>
|
render={
|
||||||
Loading...
|
() => this.renderNTPStatus(data, classes)
|
||||||
</Typography>
|
}
|
||||||
</div>
|
/>
|
||||||
:
|
|
||||||
data ? this.renderNTPStatus(data, classes)
|
|
||||||
:
|
|
||||||
<div>
|
|
||||||
<Typography variant="h4" className={classes.fetching}>
|
|
||||||
{errorMessage}
|
|
||||||
</Typography>
|
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={this.props.loadData}>
|
|
||||||
Refresh
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,28 +1,34 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import { OTA_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
import { OTA_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
||||||
import {restComponent} from '../components/RestComponent';
|
import { restComponent } from '../components/RestComponent';
|
||||||
|
import LoadingNotification from '../components/LoadingNotification';
|
||||||
import SectionContent from '../components/SectionContent';
|
import SectionContent from '../components/SectionContent';
|
||||||
import OTASettingsForm from '../forms/OTASettingsForm';
|
import OTASettingsForm from '../forms/OTASettingsForm';
|
||||||
|
|
||||||
class OTASettings extends Component {
|
class OTASettings extends Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.loadData();
|
this.props.loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { data, fetched, errorMessage } = this.props;
|
const { fetched, errorMessage, data, saveData, loadData, handleValueChange, handleCheckboxChange } = this.props;
|
||||||
return (
|
return (
|
||||||
<SectionContent title="OTA Settings">
|
<SectionContent title="OTA Settings">
|
||||||
<OTASettingsForm
|
<LoadingNotification
|
||||||
otaSettings={data}
|
onReset={loadData}
|
||||||
otaSettingsFetched={fetched}
|
fetched={fetched}
|
||||||
errorMessage={errorMessage}
|
errorMessage={errorMessage}
|
||||||
onSubmit={this.props.saveData}
|
render={() =>
|
||||||
onReset={this.props.loadData}
|
<OTASettingsForm
|
||||||
handleValueChange={this.props.handleValueChange}
|
otaSettings={data}
|
||||||
handleCheckboxChange={this.props.handleCheckboxChange}
|
onSubmit={saveData}
|
||||||
|
onReset={loadData}
|
||||||
|
handleValueChange={handleValueChange}
|
||||||
|
handleCheckboxChange={handleCheckboxChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
)
|
)
|
||||||
|
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
|||||||
|
|
||||||
import { SECURITY_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
import { SECURITY_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
||||||
import { restComponent } from '../components/RestComponent';
|
import { restComponent } from '../components/RestComponent';
|
||||||
|
import LoadingNotification from '../components/LoadingNotification';
|
||||||
import SecuritySettingsForm from '../forms/SecuritySettingsForm';
|
import SecuritySettingsForm from '../forms/SecuritySettingsForm';
|
||||||
import SectionContent from '../components/SectionContent';
|
import SectionContent from '../components/SectionContent';
|
||||||
|
|
||||||
@ -12,16 +13,21 @@ class SecuritySettings extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { data, fetched, errorMessage } = this.props;
|
const { data, fetched, errorMessage, saveData, loadData, handleValueChange } = this.props;
|
||||||
return (
|
return (
|
||||||
<SectionContent title="Security Settings">
|
<SectionContent title="Security Settings">
|
||||||
<SecuritySettingsForm
|
<LoadingNotification
|
||||||
securitySettings={data}
|
onReset={loadData}
|
||||||
securitySettingsFetched={fetched}
|
fetched={fetched}
|
||||||
errorMessage={errorMessage}
|
errorMessage={errorMessage}
|
||||||
onSubmit={this.props.saveData}
|
render={() =>
|
||||||
onReset={this.props.loadData}
|
<SecuritySettingsForm
|
||||||
handleValueChange={this.props.handleValueChange}
|
securitySettings={data}
|
||||||
|
onSubmit={saveData}
|
||||||
|
onReset={loadData}
|
||||||
|
handleValueChange={handleValueChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,7 @@ import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
|
|||||||
import Paper from '@material-ui/core/Paper';
|
import Paper from '@material-ui/core/Paper';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import Fab from '@material-ui/core/Fab';
|
import Fab from '@material-ui/core/Fab';
|
||||||
import { APP_NAME } from '../constants/App';
|
import { PROJECT_NAME } from '../constants/Env';
|
||||||
import ForwardIcon from '@material-ui/icons/Forward';
|
import ForwardIcon from '@material-ui/icons/Forward';
|
||||||
import { withSnackbar } from 'notistack';
|
import { withSnackbar } from 'notistack';
|
||||||
import { SIGN_IN_ENDPOINT } from '../constants/Endpoints';
|
import { SIGN_IN_ENDPOINT } from '../constants/Endpoints';
|
||||||
@ -97,7 +97,7 @@ class SignInPage extends Component {
|
|||||||
return (
|
return (
|
||||||
<div className={classes.loginPage}>
|
<div className={classes.loginPage}>
|
||||||
<Paper className={classes.loginPanel}>
|
<Paper className={classes.loginPanel}>
|
||||||
<Typography variant="h4">{APP_NAME}</Typography>
|
<Typography variant="h4">{PROJECT_NAME}</Typography>
|
||||||
<ValidatorForm onSubmit={this.onSubmit}>
|
<ValidatorForm onSubmit={this.onSubmit}>
|
||||||
<TextValidator
|
<TextValidator
|
||||||
disabled={processing}
|
disabled={processing}
|
||||||
|
@ -2,8 +2,6 @@ import React, { Component, Fragment } from 'react';
|
|||||||
|
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import LinearProgress from '@material-ui/core/LinearProgress';
|
|
||||||
import Typography from '@material-ui/core/Typography';
|
|
||||||
import List from '@material-ui/core/List';
|
import List from '@material-ui/core/List';
|
||||||
import ListItem from '@material-ui/core/ListItem';
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
||||||
@ -16,16 +14,12 @@ import ShowChartIcon from '@material-ui/icons/ShowChart';
|
|||||||
import SdStorageIcon from '@material-ui/icons/SdStorage';
|
import SdStorageIcon from '@material-ui/icons/SdStorage';
|
||||||
import DataUsageIcon from '@material-ui/icons/DataUsage';
|
import DataUsageIcon from '@material-ui/icons/DataUsage';
|
||||||
|
|
||||||
|
|
||||||
import { SYSTEM_STATUS_ENDPOINT } from '../constants/Endpoints';
|
import { SYSTEM_STATUS_ENDPOINT } from '../constants/Endpoints';
|
||||||
import { restComponent } from '../components/RestComponent';
|
import { restComponent } from '../components/RestComponent';
|
||||||
|
import LoadingNotification from '../components/LoadingNotification';
|
||||||
import SectionContent from '../components/SectionContent';
|
import SectionContent from '../components/SectionContent';
|
||||||
|
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
fetching: {
|
|
||||||
margin: theme.spacing(4),
|
|
||||||
textAlign: "center"
|
|
||||||
},
|
|
||||||
button: {
|
button: {
|
||||||
marginRight: theme.spacing(2),
|
marginRight: theme.spacing(2),
|
||||||
marginTop: theme.spacing(2),
|
marginTop: theme.spacing(2),
|
||||||
@ -90,7 +84,7 @@ class SystemStatus extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNTPStatus(data, classes) {
|
renderSystemStatus(data, classes) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<List>
|
<List>
|
||||||
@ -104,29 +98,17 @@ class SystemStatus extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { data, fetched, errorMessage, classes } = this.props;
|
const { data, fetched, errorMessage, loadData, classes } = this.props;
|
||||||
return (
|
return (
|
||||||
<SectionContent title="System Status">
|
<SectionContent title="System Status">
|
||||||
{
|
<LoadingNotification
|
||||||
!fetched ?
|
onReset={loadData}
|
||||||
<div>
|
fetched={fetched}
|
||||||
<LinearProgress className={classes.fetching} />
|
errorMessage={errorMessage}
|
||||||
<Typography variant="h4" className={classes.fetching}>
|
render={
|
||||||
Loading...
|
() => this.renderSystemStatus(data, classes)
|
||||||
</Typography>
|
}
|
||||||
</div>
|
/>
|
||||||
:
|
|
||||||
data ? this.renderNTPStatus(data, classes)
|
|
||||||
:
|
|
||||||
<div>
|
|
||||||
<Typography variant="h4" className={classes.fetching}>
|
|
||||||
{errorMessage}
|
|
||||||
</Typography>
|
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={this.props.loadData}>
|
|
||||||
Refresh
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { withSnackbar } from 'notistack';
|
||||||
|
|
||||||
import { SCAN_NETWORKS_ENDPOINT, LIST_NETWORKS_ENDPOINT } from '../constants/Endpoints';
|
import { SCAN_NETWORKS_ENDPOINT, LIST_NETWORKS_ENDPOINT } from '../constants/Endpoints';
|
||||||
import SectionContent from '../components/SectionContent';
|
import SectionContent from '../components/SectionContent';
|
||||||
import WiFiNetworkSelector from '../forms/WiFiNetworkSelector';
|
import WiFiNetworkSelector from '../forms/WiFiNetworkSelector';
|
||||||
import { withSnackbar } from 'notistack';
|
|
||||||
import { redirectingAuthorizedFetch } from '../authentication/Authentication';
|
import { redirectingAuthorizedFetch } from '../authentication/Authentication';
|
||||||
|
|
||||||
const NUM_POLLS = 10
|
const NUM_POLLS = 10
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { WIFI_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
import { WIFI_SETTINGS_ENDPOINT } from '../constants/Endpoints';
|
||||||
import { restComponent } from '../components/RestComponent';
|
import { restComponent } from '../components/RestComponent';
|
||||||
|
import LoadingNotification from '../components/LoadingNotification';
|
||||||
import SectionContent from '../components/SectionContent';
|
import SectionContent from '../components/SectionContent';
|
||||||
import WiFiSettingsForm from '../forms/WiFiSettingsForm';
|
import WiFiSettingsForm from '../forms/WiFiSettingsForm';
|
||||||
|
|
||||||
@ -18,10 +19,10 @@ class WiFiSettings extends Component {
|
|||||||
const { selectedNetwork } = this.props;
|
const { selectedNetwork } = this.props;
|
||||||
if (selectedNetwork) {
|
if (selectedNetwork) {
|
||||||
var wifiSettings = {
|
var wifiSettings = {
|
||||||
ssid:selectedNetwork.ssid,
|
ssid: selectedNetwork.ssid,
|
||||||
password:"",
|
password: "",
|
||||||
hostname:"esp8266-react",
|
hostname: "esp8266-react",
|
||||||
static_ip_config:false,
|
static_ip_config: false,
|
||||||
}
|
}
|
||||||
this.props.setData(wifiSettings);
|
this.props.setData(wifiSettings);
|
||||||
} else {
|
} else {
|
||||||
@ -35,19 +36,24 @@ class WiFiSettings extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { data, fetched, errorMessage, selectedNetwork } = this.props;
|
const { data, fetched, errorMessage, saveData, loadData, handleValueChange, handleCheckboxChange, selectedNetwork, deselectNetwork } = this.props;
|
||||||
return (
|
return (
|
||||||
<SectionContent title="WiFi Settings">
|
<SectionContent title="WiFi Settings">
|
||||||
<WiFiSettingsForm
|
<LoadingNotification
|
||||||
wifiSettings={data}
|
onReset={loadData}
|
||||||
wifiSettingsFetched={fetched}
|
fetched={fetched}
|
||||||
errorMessage={errorMessage}
|
errorMessage={errorMessage}
|
||||||
selectedNetwork={selectedNetwork}
|
render={() =>
|
||||||
deselectNetwork={this.props.deselectNetwork}
|
<WiFiSettingsForm
|
||||||
onSubmit={this.props.saveData}
|
wifiSettings={data}
|
||||||
onReset={this.deselectNetworkAndLoadData}
|
selectedNetwork={selectedNetwork}
|
||||||
handleValueChange={this.props.handleValueChange}
|
deselectNetwork={deselectNetwork}
|
||||||
handleCheckboxChange={this.props.handleCheckboxChange}
|
onSubmit={saveData}
|
||||||
|
onReset={this.deselectNetworkAndLoadData}
|
||||||
|
handleValueChange={handleValueChange}
|
||||||
|
handleCheckboxChange={handleCheckboxChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
)
|
)
|
||||||
|
@ -2,14 +2,10 @@ import React, { Component, Fragment } from 'react';
|
|||||||
|
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import LinearProgress from '@material-ui/core/LinearProgress';
|
|
||||||
import Typography from '@material-ui/core/Typography';
|
|
||||||
|
|
||||||
import List from '@material-ui/core/List';
|
import List from '@material-ui/core/List';
|
||||||
import ListItem from '@material-ui/core/ListItem';
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
import ListItemText from '@material-ui/core/ListItemText';
|
import ListItemText from '@material-ui/core/ListItemText';
|
||||||
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
||||||
|
|
||||||
import Avatar from '@material-ui/core/Avatar';
|
import Avatar from '@material-ui/core/Avatar';
|
||||||
import Divider from '@material-ui/core/Divider';
|
import Divider from '@material-ui/core/Divider';
|
||||||
import WifiIcon from '@material-ui/icons/Wifi';
|
import WifiIcon from '@material-ui/icons/Wifi';
|
||||||
@ -23,6 +19,7 @@ import { WIFI_STATUS_ENDPOINT } from '../constants/Endpoints';
|
|||||||
import { isConnected, connectionStatus, connectionStatusHighlight } from '../constants/WiFiConnectionStatus';
|
import { isConnected, connectionStatus, connectionStatusHighlight } from '../constants/WiFiConnectionStatus';
|
||||||
import * as Highlight from '../constants/Highlight';
|
import * as Highlight from '../constants/Highlight';
|
||||||
import { restComponent } from '../components/RestComponent';
|
import { restComponent } from '../components/RestComponent';
|
||||||
|
import LoadingNotification from '../components/LoadingNotification';
|
||||||
|
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
["wifiStatus_" + Highlight.IDLE]: {
|
["wifiStatus_" + Highlight.IDLE]: {
|
||||||
@ -37,10 +34,6 @@ const styles = theme => ({
|
|||||||
["wifiStatus_" + Highlight.WARN]: {
|
["wifiStatus_" + Highlight.WARN]: {
|
||||||
backgroundColor: theme.palette.highlight_warn
|
backgroundColor: theme.palette.highlight_warn
|
||||||
},
|
},
|
||||||
fetching: {
|
|
||||||
margin: theme.spacing(4),
|
|
||||||
textAlign: "center"
|
|
||||||
},
|
|
||||||
button: {
|
button: {
|
||||||
marginRight: theme.spacing(2),
|
marginRight: theme.spacing(2),
|
||||||
marginTop: theme.spacing(2),
|
marginTop: theme.spacing(2),
|
||||||
@ -145,32 +138,21 @@ class WiFiStatus extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { data, fetched, errorMessage, classes } = this.props;
|
const { data, fetched, errorMessage, loadData, classes } = this.props;
|
||||||
return (
|
return (
|
||||||
<SectionContent title="WiFi Status">
|
<SectionContent title="WiFi Status">
|
||||||
{
|
<LoadingNotification
|
||||||
!fetched ?
|
onReset={loadData}
|
||||||
<div>
|
fetched={fetched}
|
||||||
<LinearProgress className={classes.fetching} />
|
errorMessage={errorMessage}
|
||||||
<Typography variant="h4" className={classes.fetching}>
|
render={
|
||||||
Loading...
|
() => this.renderWiFiStatus(data, classes)
|
||||||
</Typography>
|
}
|
||||||
</div>
|
/>
|
||||||
:
|
|
||||||
data ? this.renderWiFiStatus(data, classes)
|
|
||||||
:
|
|
||||||
<div>
|
|
||||||
<Typography variant="h4" className={classes.fetching}>
|
|
||||||
{errorMessage}
|
|
||||||
</Typography>
|
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={this.props.loadData}>
|
|
||||||
Refresh
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default restComponent(WIFI_STATUS_ENDPOINT, withStyles(styles)(WiFiStatus));
|
export default restComponent(WIFI_STATUS_ENDPOINT, withStyles(styles)(WiFiStatus));
|
||||||
|
@ -1,29 +1,19 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { TextValidator, ValidatorForm, SelectValidator } from 'react-material-ui-form-validator';
|
||||||
|
|
||||||
|
import { isAPEnabled } from '../constants/WiFiAPModes';
|
||||||
|
import PasswordValidator from '../components/PasswordValidator';
|
||||||
|
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import LinearProgress from '@material-ui/core/LinearProgress';
|
|
||||||
import Typography from '@material-ui/core/Typography';
|
|
||||||
import MenuItem from '@material-ui/core/MenuItem';
|
import MenuItem from '@material-ui/core/MenuItem';
|
||||||
|
|
||||||
import { TextValidator, ValidatorForm, SelectValidator } from 'react-material-ui-form-validator';
|
|
||||||
|
|
||||||
import {isAPEnabled} from '../constants/WiFiAPModes';
|
|
||||||
import PasswordValidator from '../components/PasswordValidator';
|
|
||||||
|
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
loadingSettings: {
|
|
||||||
margin: theme.spacing(0.5),
|
|
||||||
},
|
|
||||||
loadingSettingsDetails: {
|
|
||||||
margin: theme.spacing(4),
|
|
||||||
textAlign: "center"
|
|
||||||
},
|
|
||||||
textField: {
|
textField: {
|
||||||
width: "100%"
|
width: "100%"
|
||||||
},
|
},
|
||||||
selectField:{
|
selectField: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
marginTop: theme.spacing(2),
|
marginTop: theme.spacing(2),
|
||||||
marginBottom: theme.spacing(0.5)
|
marginBottom: theme.spacing(0.5)
|
||||||
@ -37,86 +27,54 @@ const styles = theme => ({
|
|||||||
class APSettingsForm extends React.Component {
|
class APSettingsForm extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { classes, apSettingsFetched, apSettings, errorMessage, handleValueChange, onSubmit, onReset } = this.props;
|
const { classes, apSettings, handleValueChange, onSubmit, onReset } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<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>
|
||||||
{
|
{
|
||||||
!apSettingsFetched ?
|
isAPEnabled(apSettings.provision_mode) &&
|
||||||
|
<Fragment>
|
||||||
<div className={classes.loadingSettings}>
|
<TextValidator
|
||||||
<LinearProgress className={classes.loadingSettingsDetails}/>
|
validators={['required', 'matchRegexp:^.{1,32}$']}
|
||||||
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
errorMessages={['Access Point SSID is required', 'Access Point SSID must be 32 characters or less']}
|
||||||
Loading...
|
name="ssid"
|
||||||
</Typography>
|
label="Access Point SSID"
|
||||||
</div>
|
className={classes.textField}
|
||||||
|
value={apSettings.ssid}
|
||||||
: apSettings ?
|
onChange={handleValueChange('ssid')}
|
||||||
|
margin="normal"
|
||||||
<ValidatorForm onSubmit={onSubmit} ref="APSettingsForm">
|
/>
|
||||||
|
<PasswordValidator
|
||||||
<SelectValidator name="provision_mode" label="Provide Access Point..." value={apSettings.provision_mode} className={classes.selectField}
|
validators={['required', 'matchRegexp:^.{1,64}$']}
|
||||||
onChange={handleValueChange('provision_mode')}>
|
errorMessages={['Access Point Password is required', 'Access Point Password must be 64 characters or less']}
|
||||||
<MenuItem value={0}>Always</MenuItem>
|
name="password"
|
||||||
<MenuItem value={1}>When WiFi Disconnected</MenuItem>
|
label="Access Point Password"
|
||||||
<MenuItem value={2}>Never</MenuItem>
|
className={classes.textField}
|
||||||
</SelectValidator>
|
value={apSettings.password}
|
||||||
|
onChange={handleValueChange('password')}
|
||||||
{
|
margin="normal"
|
||||||
isAPEnabled(apSettings.provision_mode) &&
|
/>
|
||||||
<Fragment>
|
</Fragment>
|
||||||
<TextValidator
|
}
|
||||||
validators={['required', 'matchRegexp:^.{1,32}$']}
|
<Button variant="contained" color="primary" className={classes.button} type="submit">
|
||||||
errorMessages={['Access Point SSID is required', 'Access Point SSID must be 32 characters or less']}
|
Save
|
||||||
name="ssid"
|
</Button>
|
||||||
label="Access Point SSID"
|
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
||||||
className={classes.textField}
|
Reset
|
||||||
value={apSettings.ssid}
|
</Button>
|
||||||
onChange={handleValueChange('ssid')}
|
</ValidatorForm>
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
<PasswordValidator
|
|
||||||
validators={['required', 'matchRegexp:^.{1,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"
|
|
||||||
/>
|
|
||||||
</Fragment>
|
|
||||||
}
|
|
||||||
|
|
||||||
<Button variant="contained" color="primary" className={classes.button} type="submit">
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
</ValidatorForm>
|
|
||||||
|
|
||||||
:
|
|
||||||
|
|
||||||
<div className={classes.loadingSettings}>
|
|
||||||
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
|
||||||
{errorMessage}
|
|
||||||
</Typography>
|
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
APSettingsForm.propTypes = {
|
APSettingsForm.propTypes = {
|
||||||
classes: PropTypes.object.isRequired,
|
classes: PropTypes.object.isRequired,
|
||||||
apSettingsFetched: PropTypes.bool.isRequired,
|
|
||||||
apSettings: PropTypes.object,
|
apSettings: PropTypes.object,
|
||||||
errorMessage: PropTypes.string,
|
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
onReset: PropTypes.func.isRequired,
|
onReset: PropTypes.func.isRequired,
|
||||||
handleValueChange: PropTypes.func.isRequired
|
handleValueChange: PropTypes.func.isRequired
|
||||||
|
@ -5,7 +5,6 @@ import { ValidatorForm } from 'react-material-ui-form-validator';
|
|||||||
|
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import LinearProgress from '@material-ui/core/LinearProgress';
|
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import Table from '@material-ui/core/Table';
|
import Table from '@material-ui/core/Table';
|
||||||
import TableBody from '@material-ui/core/TableBody';
|
import TableBody from '@material-ui/core/TableBody';
|
||||||
@ -14,7 +13,6 @@ import TableFooter from '@material-ui/core/TableFooter';
|
|||||||
import TableHead from '@material-ui/core/TableHead';
|
import TableHead from '@material-ui/core/TableHead';
|
||||||
import TableRow from '@material-ui/core/TableRow';
|
import TableRow from '@material-ui/core/TableRow';
|
||||||
import Box from '@material-ui/core/Box';
|
import Box from '@material-ui/core/Box';
|
||||||
|
|
||||||
import EditIcon from '@material-ui/icons/Edit';
|
import EditIcon from '@material-ui/icons/Edit';
|
||||||
import DeleteIcon from '@material-ui/icons/Delete';
|
import DeleteIcon from '@material-ui/icons/Delete';
|
||||||
import CloseIcon from '@material-ui/icons/Close';
|
import CloseIcon from '@material-ui/icons/Close';
|
||||||
@ -25,22 +23,12 @@ import UserForm from './UserForm';
|
|||||||
import { withAuthenticationContext } from '../authentication/Context';
|
import { withAuthenticationContext } from '../authentication/Context';
|
||||||
|
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
loadingSettings: {
|
|
||||||
margin: theme.spacing(0.5),
|
|
||||||
},
|
|
||||||
loadingSettingsDetails: {
|
|
||||||
margin: theme.spacing(4),
|
|
||||||
textAlign: "center"
|
|
||||||
},
|
|
||||||
button: {
|
button: {
|
||||||
marginRight: theme.spacing(2),
|
marginRight: theme.spacing(2),
|
||||||
marginTop: theme.spacing(2),
|
marginTop: theme.spacing(2),
|
||||||
},
|
},
|
||||||
table: {
|
table: {
|
||||||
'& td, & th': { padding: theme.spacing(0.5) }
|
'& td, & th': { padding: theme.spacing(0.5) }
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
whiteSpace: "nowrap"
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -134,98 +122,80 @@ class ManageUsersForm extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { classes, userData, userDataFetched, errorMessage, onReset } = this.props;
|
const { classes, userData, onReset } = this.props;
|
||||||
const { user, creating } = this.state;
|
const { user, creating } = this.state;
|
||||||
return (
|
return (
|
||||||
!userDataFetched ?
|
<Fragment>
|
||||||
<div className={classes.loadingSettings}>
|
<ValidatorForm onSubmit={this.onSubmit}>
|
||||||
<LinearProgress className={classes.loadingSettingsDetails} />
|
<Table className={classes.table}>
|
||||||
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
<TableHead>
|
||||||
Loading...
|
<TableRow>
|
||||||
</Typography>
|
<TableCell>Username</TableCell>
|
||||||
</div>
|
<TableCell align="center">Admin?</TableCell>
|
||||||
:
|
<TableCell />
|
||||||
userData ?
|
</TableRow>
|
||||||
<Fragment>
|
</TableHead>
|
||||||
<ValidatorForm onSubmit={this.onSubmit}>
|
<TableBody>
|
||||||
<Table className={classes.table}>
|
{userData.users.sort(compareUsers).map(user => (
|
||||||
<TableHead>
|
<TableRow key={user.username}>
|
||||||
<TableRow>
|
<TableCell component="th" scope="row">
|
||||||
<TableCell>Username</TableCell>
|
{user.username}
|
||||||
<TableCell align="center">Admin?</TableCell>
|
</TableCell>
|
||||||
<TableCell />
|
<TableCell align="center">
|
||||||
</TableRow>
|
{
|
||||||
</TableHead>
|
user.admin ? <CheckIcon /> : <CloseIcon />
|
||||||
<TableBody>
|
}
|
||||||
{userData.users.sort(compareUsers).map(user => (
|
</TableCell>
|
||||||
<TableRow key={user.username}>
|
<TableCell align="center">
|
||||||
<TableCell component="th" scope="row">
|
<IconButton aria-label="Delete" onClick={() => this.removeUser(user)}>
|
||||||
{user.username}
|
<DeleteIcon />
|
||||||
</TableCell>
|
</IconButton>
|
||||||
<TableCell align="center">
|
<IconButton aria-label="Edit" onClick={() => this.startEditingUser(user)}>
|
||||||
{
|
<EditIcon />
|
||||||
user.admin ? <CheckIcon /> : <CloseIcon />
|
</IconButton>
|
||||||
}
|
</TableCell>
|
||||||
</TableCell>
|
</TableRow>
|
||||||
<TableCell align="center">
|
))}
|
||||||
<IconButton aria-label="Delete" onClick={() => this.removeUser(user)}>
|
</TableBody>
|
||||||
<DeleteIcon />
|
<TableFooter>
|
||||||
</IconButton>
|
<TableRow>
|
||||||
<IconButton aria-label="Edit" onClick={() => this.startEditingUser(user)}>
|
<TableCell colSpan={2} />
|
||||||
<EditIcon />
|
<TableCell align="center">
|
||||||
</IconButton>
|
<Button variant="contained" color="secondary" onClick={this.createUser}>
|
||||||
</TableCell>
|
Add User
|
||||||
</TableRow>
|
</Button>
|
||||||
))}
|
</TableCell>
|
||||||
</TableBody>
|
</TableRow>
|
||||||
<TableFooter>
|
</TableFooter>
|
||||||
<TableRow>
|
</Table>
|
||||||
<TableCell colSpan={2} />
|
{
|
||||||
<TableCell align="center">
|
this.noAdminConfigured() &&
|
||||||
<Button variant="contained" color="secondary" onClick={this.createUser}>
|
<Typography component="div" variant="body1">
|
||||||
Add User
|
<Box bgcolor="error.main" color="error.contrastText" p={2} mt={2} mb={2}>
|
||||||
</Button>
|
You must have at least one admin user configured.
|
||||||
</TableCell>
|
</Box>
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
|
||||||
{
|
|
||||||
this.noAdminConfigured() &&
|
|
||||||
<Typography component="div" variant="body1">
|
|
||||||
<Box bgcolor="error.main" color="error.contrastText" p={2} mt={2} mb={2}>
|
|
||||||
You must have at least one admin user configured.
|
|
||||||
</Box>
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
<Button variant="contained" color="primary" className={classes.button} type="submit" disabled={this.noAdminConfigured()}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
</ValidatorForm>
|
|
||||||
{
|
|
||||||
user &&
|
|
||||||
<UserForm
|
|
||||||
user={user}
|
|
||||||
creating={creating}
|
|
||||||
onDoneEditing={this.doneEditingUser}
|
|
||||||
onCancelEditing={this.cancelEditingUser}
|
|
||||||
handleValueChange={this.handleUserValueChange}
|
|
||||||
handleCheckboxChange={this.handleUserCheckboxChange}
|
|
||||||
uniqueUsername={this.uniqueUsername}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</Fragment>
|
|
||||||
:
|
|
||||||
<div className={classes.loadingSettings}>
|
|
||||||
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
|
||||||
{errorMessage}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
}
|
||||||
Reset
|
<Button variant="contained" color="primary" className={classes.button} type="submit" disabled={this.noAdminConfigured()}>
|
||||||
</Button>
|
Save
|
||||||
</div>
|
</Button>
|
||||||
|
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</ValidatorForm>
|
||||||
|
{
|
||||||
|
user &&
|
||||||
|
<UserForm
|
||||||
|
user={user}
|
||||||
|
creating={creating}
|
||||||
|
onDoneEditing={this.doneEditingUser}
|
||||||
|
onCancelEditing={this.cancelEditingUser}
|
||||||
|
handleValueChange={this.handleUserValueChange}
|
||||||
|
handleCheckboxChange={this.handleUserCheckboxChange}
|
||||||
|
uniqueUsername={this.uniqueUsername}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,8 +204,6 @@ class ManageUsersForm extends React.Component {
|
|||||||
ManageUsersForm.propTypes = {
|
ManageUsersForm.propTypes = {
|
||||||
classes: PropTypes.object.isRequired,
|
classes: PropTypes.object.isRequired,
|
||||||
userData: PropTypes.object,
|
userData: PropTypes.object,
|
||||||
userDataFetched: PropTypes.bool.isRequired,
|
|
||||||
errorMessage: PropTypes.string,
|
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
onReset: PropTypes.func.isRequired,
|
onReset: PropTypes.func.isRequired,
|
||||||
setData: PropTypes.func.isRequired,
|
setData: PropTypes.func.isRequired,
|
||||||
|
@ -1,24 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
|
||||||
|
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import LinearProgress from '@material-ui/core/LinearProgress';
|
|
||||||
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
|
|
||||||
import Typography from '@material-ui/core/Typography';
|
|
||||||
|
|
||||||
import isIP from '../validators/isIP';
|
import isIP from '../validators/isIP';
|
||||||
import isHostname from '../validators/isHostname';
|
import isHostname from '../validators/isHostname';
|
||||||
import or from '../validators/or';
|
import or from '../validators/or';
|
||||||
|
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
loadingSettings: {
|
|
||||||
margin: theme.spacing(0.5),
|
|
||||||
},
|
|
||||||
loadingSettingsDetails: {
|
|
||||||
margin: theme.spacing(4),
|
|
||||||
textAlign: "center"
|
|
||||||
},
|
|
||||||
textField: {
|
textField: {
|
||||||
width: "100%"
|
width: "100%"
|
||||||
},
|
},
|
||||||
@ -35,76 +26,44 @@ class NTPSettingsForm extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { classes, ntpSettingsFetched, ntpSettings, errorMessage, handleValueChange, onSubmit, onReset } = this.props;
|
const { classes, ntpSettings, handleValueChange, onSubmit, onReset } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<ValidatorForm onSubmit={onSubmit}>
|
||||||
{
|
<TextValidator
|
||||||
!ntpSettingsFetched ?
|
validators={['required', 'isIPOrHostname']}
|
||||||
|
errorMessages={['Server is required', "Not a valid IP address or hostname"]}
|
||||||
<div className={classes.loadingSettings}>
|
name="server"
|
||||||
<LinearProgress className={classes.loadingSettingsDetails}/>
|
label="Server"
|
||||||
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
className={classes.textField}
|
||||||
Loading...
|
value={ntpSettings.server}
|
||||||
</Typography>
|
onChange={handleValueChange('server')}
|
||||||
</div>
|
margin="normal"
|
||||||
|
/>
|
||||||
: ntpSettings ?
|
<TextValidator
|
||||||
|
validators={['required', 'isNumber', 'minNumber:60', 'maxNumber:86400']}
|
||||||
<ValidatorForm onSubmit={onSubmit}>
|
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"
|
||||||
<TextValidator
|
label="Interval (Seconds)"
|
||||||
validators={['required', 'isIPOrHostname']}
|
className={classes.textField}
|
||||||
errorMessages={['Server is required', "Not a valid IP address or hostname"]}
|
value={ntpSettings.interval}
|
||||||
name="server"
|
type="number"
|
||||||
label="Server"
|
onChange={handleValueChange('interval')}
|
||||||
className={classes.textField}
|
margin="normal"
|
||||||
value={ntpSettings.server}
|
/>
|
||||||
onChange={handleValueChange('server')}
|
<Button variant="contained" color="primary" className={classes.button} type="submit">
|
||||||
margin="normal"
|
Save
|
||||||
/>
|
</Button>
|
||||||
|
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
||||||
<TextValidator
|
Reset
|
||||||
validators={['required','isNumber','minNumber:60','maxNumber:86400']}
|
</Button>
|
||||||
errorMessages={['Interval is required','Interval must be a number','Must be at least 60 seconds',"Must not be more than 86400 seconds (24 hours)"]}
|
</ValidatorForm>
|
||||||
name="interval"
|
|
||||||
label="Interval (Seconds)"
|
|
||||||
className={classes.textField}
|
|
||||||
value={ntpSettings.interval}
|
|
||||||
type="number"
|
|
||||||
onChange={handleValueChange('interval')}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button variant="contained" color="primary" className={classes.button} type="submit">
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
</ValidatorForm>
|
|
||||||
|
|
||||||
:
|
|
||||||
|
|
||||||
<div className={classes.loadingSettings}>
|
|
||||||
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
|
||||||
{errorMessage}
|
|
||||||
</Typography>
|
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NTPSettingsForm.propTypes = {
|
NTPSettingsForm.propTypes = {
|
||||||
classes: PropTypes.object.isRequired,
|
classes: PropTypes.object.isRequired,
|
||||||
ntpSettingsFetched: PropTypes.bool.isRequired,
|
|
||||||
ntpSettings: PropTypes.object,
|
ntpSettings: PropTypes.object,
|
||||||
errorMessage: PropTypes.string,
|
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
onReset: PropTypes.func.isRequired,
|
onReset: PropTypes.func.isRequired,
|
||||||
handleValueChange: PropTypes.func.isRequired,
|
handleValueChange: PropTypes.func.isRequired,
|
||||||
|
@ -4,9 +4,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import Switch from '@material-ui/core/Switch';
|
import Switch from '@material-ui/core/Switch';
|
||||||
import LinearProgress from '@material-ui/core/LinearProgress';
|
|
||||||
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
|
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
|
||||||
import Typography from '@material-ui/core/Typography';
|
|
||||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||||
|
|
||||||
import isIP from '../validators/isIP';
|
import isIP from '../validators/isIP';
|
||||||
@ -15,13 +13,6 @@ import or from '../validators/or';
|
|||||||
import PasswordValidator from '../components/PasswordValidator';
|
import PasswordValidator from '../components/PasswordValidator';
|
||||||
|
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
loadingSettings: {
|
|
||||||
margin: theme.spacing(0.5),
|
|
||||||
},
|
|
||||||
loadingSettingsDetails: {
|
|
||||||
margin: theme.spacing(4),
|
|
||||||
textAlign: "center"
|
|
||||||
},
|
|
||||||
switchControl: {
|
switchControl: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
marginTop: theme.spacing(2),
|
marginTop: theme.spacing(2),
|
||||||
@ -43,88 +34,55 @@ class OTASettingsForm extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { classes, otaSettingsFetched, otaSettings, errorMessage, handleValueChange, handleCheckboxChange, onSubmit, onReset } = this.props;
|
const { classes, otaSettings, handleValueChange, handleCheckboxChange, onSubmit, onReset } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<ValidatorForm onSubmit={onSubmit}>
|
||||||
{
|
<FormControlLabel className={classes.switchControl}
|
||||||
!otaSettingsFetched ?
|
control={
|
||||||
|
<Switch
|
||||||
<div className={classes.loadingSettings}>
|
checked={otaSettings.enabled}
|
||||||
<LinearProgress className={classes.loadingSettingsDetails}/>
|
onChange={handleCheckboxChange('enabled')}
|
||||||
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
value="enabled"
|
||||||
Loading...
|
color="primary"
|
||||||
</Typography>
|
/>
|
||||||
</div>
|
}
|
||||||
|
label="Enable OTA Updates?"
|
||||||
: otaSettings ?
|
/>
|
||||||
|
<TextValidator
|
||||||
<ValidatorForm onSubmit={onSubmit}>
|
validators={['required', 'isNumber', 'minNumber:1025', 'maxNumber:65535']}
|
||||||
|
errorMessages={['Port is required', "Must be a number", "Must be greater than 1024 ", "Max value is 65535"]}
|
||||||
<FormControlLabel className={classes.switchControl}
|
name="port"
|
||||||
control={
|
label="Port"
|
||||||
<Switch
|
className={classes.textField}
|
||||||
checked={otaSettings.enabled}
|
value={otaSettings.port}
|
||||||
onChange={handleCheckboxChange('enabled')}
|
type="number"
|
||||||
value="enabled"
|
onChange={handleValueChange('port')}
|
||||||
color="primary"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
}
|
<PasswordValidator
|
||||||
label="Enable OTA Updates?"
|
validators={['required', 'matchRegexp:^.{1,64}$']}
|
||||||
/>
|
errorMessages={['OTA Password is required', 'OTA Point Password must be 64 characters or less']}
|
||||||
|
name="password"
|
||||||
<TextValidator
|
label="Password"
|
||||||
validators={['required', 'isNumber', 'minNumber:1025', 'maxNumber:65535']}
|
className={classes.textField}
|
||||||
errorMessages={['Port is required', "Must be a number", "Must be greater than 1024 ", "Max value is 65535"]}
|
value={otaSettings.password}
|
||||||
name="port"
|
onChange={handleValueChange('password')}
|
||||||
label="Port"
|
margin="normal"
|
||||||
className={classes.textField}
|
/>
|
||||||
value={otaSettings.port}
|
<Button variant="contained" color="primary" className={classes.button} type="submit">
|
||||||
type="number"
|
Save
|
||||||
onChange={handleValueChange('port')}
|
</Button>
|
||||||
margin="normal"
|
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
||||||
/>
|
Reset
|
||||||
|
</Button>
|
||||||
<PasswordValidator
|
</ValidatorForm>
|
||||||
validators={['required', 'matchRegexp:^.{1,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="contained" color="primary" className={classes.button} type="submit">
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
</ValidatorForm>
|
|
||||||
|
|
||||||
:
|
|
||||||
|
|
||||||
<div className={classes.loadingSettings}>
|
|
||||||
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
|
||||||
{errorMessage}
|
|
||||||
</Typography>
|
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OTASettingsForm.propTypes = {
|
OTASettingsForm.propTypes = {
|
||||||
classes: PropTypes.object.isRequired,
|
classes: PropTypes.object.isRequired,
|
||||||
otaSettingsFetched: PropTypes.bool.isRequired,
|
|
||||||
otaSettings: PropTypes.object,
|
otaSettings: PropTypes.object,
|
||||||
errorMessage: PropTypes.string,
|
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
onReset: PropTypes.func.isRequired,
|
onReset: PropTypes.func.isRequired,
|
||||||
handleValueChange: PropTypes.func.isRequired,
|
handleValueChange: PropTypes.func.isRequired,
|
||||||
|
@ -4,7 +4,6 @@ import { ValidatorForm } from 'react-material-ui-form-validator';
|
|||||||
|
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import LinearProgress from '@material-ui/core/LinearProgress';
|
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import Box from '@material-ui/core/Box';
|
import Box from '@material-ui/core/Box';
|
||||||
|
|
||||||
@ -12,13 +11,6 @@ import PasswordValidator from '../components/PasswordValidator';
|
|||||||
import { withAuthenticationContext } from '../authentication/Context';
|
import { withAuthenticationContext } from '../authentication/Context';
|
||||||
|
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
loadingSettings: {
|
|
||||||
margin: theme.spacing(0.5),
|
|
||||||
},
|
|
||||||
loadingSettingsDetails: {
|
|
||||||
margin: theme.spacing(4),
|
|
||||||
textAlign: "center"
|
|
||||||
},
|
|
||||||
textField: {
|
textField: {
|
||||||
width: "100%"
|
width: "100%"
|
||||||
},
|
},
|
||||||
@ -36,58 +28,38 @@ class SecuritySettingsForm extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { classes, securitySettingsFetched, securitySettings, errorMessage, handleValueChange, onReset } = this.props;
|
const { classes, securitySettings, handleValueChange, onReset } = this.props;
|
||||||
return (
|
return (
|
||||||
!securitySettingsFetched ?
|
<ValidatorForm onSubmit={this.onSubmit} ref="SecuritySettingsForm">
|
||||||
<div className={classes.loadingSettings}>
|
<PasswordValidator
|
||||||
<LinearProgress className={classes.loadingSettingsDetails} />
|
validators={['required', 'matchRegexp:^.{1,64}$']}
|
||||||
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
errorMessages={['JWT Secret Required', 'JWT Secret must be 64 characters or less']}
|
||||||
Loading...
|
name="jwt_secret"
|
||||||
</Typography>
|
label="JWT Secret"
|
||||||
</div>
|
className={classes.textField}
|
||||||
:
|
value={securitySettings.jwt_secret}
|
||||||
securitySettings ?
|
onChange={handleValueChange('jwt_secret')}
|
||||||
<ValidatorForm onSubmit={this.onSubmit} ref="SecuritySettingsForm">
|
margin="normal"
|
||||||
<PasswordValidator
|
/>
|
||||||
validators={['required', 'matchRegexp:^.{1,64}$']}
|
<Typography component="div" variant="body1">
|
||||||
errorMessages={['JWT Secret Required', 'JWT Secret must be 64 characters or less']}
|
<Box bgcolor="primary.main" color="primary.contrastText" p={2} mt={2} mb={2}>
|
||||||
name="jwt_secret"
|
If you modify the JWT Secret, all users will be logged out.
|
||||||
label="JWT Secret"
|
</Box>
|
||||||
className={classes.textField}
|
</Typography>
|
||||||
value={securitySettings.jwt_secret}
|
<Button variant="contained" color="primary" className={classes.button} type="submit">
|
||||||
onChange={handleValueChange('jwt_secret')}
|
Save
|
||||||
margin="normal"
|
</Button>
|
||||||
/>
|
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
||||||
<Typography component="div" variant="body1">
|
Reset
|
||||||
<Box bgcolor="primary.main" color="primary.contrastText" p={2} mt={2} mb={2}>
|
</Button>
|
||||||
If you modify the JWT Secret, all users will be logged out.
|
</ValidatorForm>
|
||||||
</Box>
|
|
||||||
</Typography>
|
|
||||||
<Button variant="contained" color="primary" className={classes.button} type="submit">
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
</ValidatorForm>
|
|
||||||
:
|
|
||||||
<div className={classes.loadingSettings}>
|
|
||||||
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
|
||||||
{errorMessage}
|
|
||||||
</Typography>
|
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SecuritySettingsForm.propTypes = {
|
SecuritySettingsForm.propTypes = {
|
||||||
classes: PropTypes.object.isRequired,
|
classes: PropTypes.object.isRequired,
|
||||||
securitySettingsFetched: PropTypes.bool.isRequired,
|
|
||||||
securitySettings: PropTypes.object,
|
securitySettings: PropTypes.object,
|
||||||
errorMessage: PropTypes.string,
|
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
onReset: PropTypes.func.isRequired,
|
onReset: PropTypes.func.isRequired,
|
||||||
handleValueChange: PropTypes.func.isRequired,
|
handleValueChange: PropTypes.func.isRequired,
|
||||||
|
@ -69,7 +69,7 @@ class WiFiNetworkSelector extends Component {
|
|||||||
scanningForNetworks ?
|
scanningForNetworks ?
|
||||||
<div>
|
<div>
|
||||||
<LinearProgress className={classes.scanningProgress}/>
|
<LinearProgress className={classes.scanningProgress}/>
|
||||||
<Typography variant="h4" className={classes.scanningProgress}>
|
<Typography variant="h6" className={classes.scanningProgress}>
|
||||||
Scanning...
|
Scanning...
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
@ -80,7 +80,7 @@ class WiFiNetworkSelector extends Component {
|
|||||||
</List>
|
</List>
|
||||||
:
|
:
|
||||||
<div>
|
<div>
|
||||||
<Typography variant="h4" className={classes.scanningProgress}>
|
<Typography variant="h6" className={classes.scanningProgress}>
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,10 +3,8 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import LinearProgress from '@material-ui/core/LinearProgress';
|
|
||||||
import Checkbox from '@material-ui/core/Checkbox';
|
import Checkbox from '@material-ui/core/Checkbox';
|
||||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||||
import Typography from '@material-ui/core/Typography';
|
|
||||||
import List from '@material-ui/core/List';
|
import List from '@material-ui/core/List';
|
||||||
import ListItem from '@material-ui/core/ListItem';
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
import ListItemText from '@material-ui/core/ListItemText';
|
import ListItemText from '@material-ui/core/ListItemText';
|
||||||
@ -28,13 +26,6 @@ import optional from '../validators/optional';
|
|||||||
import PasswordValidator from '../components/PasswordValidator';
|
import PasswordValidator from '../components/PasswordValidator';
|
||||||
|
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
loadingSettings: {
|
|
||||||
margin: theme.spacing(0.5),
|
|
||||||
},
|
|
||||||
loadingSettingsDetails: {
|
|
||||||
margin: theme.spacing(4),
|
|
||||||
textAlign: "center"
|
|
||||||
},
|
|
||||||
textField: {
|
textField: {
|
||||||
width: "100%"
|
width: "100%"
|
||||||
},
|
},
|
||||||
@ -80,157 +71,124 @@ class WiFiSettingsForm extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { classes, wifiSettingsFetched, wifiSettings, errorMessage, selectedNetwork, handleValueChange, handleCheckboxChange, onSubmit, onReset } = this.props;
|
const { classes, wifiSettings, selectedNetwork, handleValueChange, handleCheckboxChange, onSubmit, onReset } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<ValidatorForm onSubmit={onSubmit} ref="WiFiSettingsForm">
|
||||||
{
|
{
|
||||||
!wifiSettingsFetched ?
|
selectedNetwork ? this.renderSelectedNetwork() :
|
||||||
|
<TextValidator
|
||||||
<div className={classes.loadingSettings}>
|
validators={['matchRegexp:^.{0,32}$']}
|
||||||
<LinearProgress className={classes.loadingSettingsDetails} />
|
errorMessages={['SSID must be 32 characters or less']}
|
||||||
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
name="ssid"
|
||||||
Loading...
|
label="SSID"
|
||||||
</Typography>
|
className={classes.textField}
|
||||||
</div>
|
value={wifiSettings.ssid}
|
||||||
|
onChange={handleValueChange('ssid')}
|
||||||
: wifiSettings ?
|
margin="normal"
|
||||||
|
/>
|
||||||
<ValidatorForm onSubmit={onSubmit} ref="WiFiSettingsForm">
|
|
||||||
{
|
|
||||||
selectedNetwork ? this.renderSelectedNetwork() :
|
|
||||||
<TextValidator
|
|
||||||
validators={['matchRegexp:^.{0,32}$']}
|
|
||||||
errorMessages={['SSID must be 32 characters or less']}
|
|
||||||
name="ssid"
|
|
||||||
label="SSID"
|
|
||||||
className={classes.textField}
|
|
||||||
value={wifiSettings.ssid}
|
|
||||||
onChange={handleValueChange('ssid')}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
{
|
|
||||||
!isNetworkOpen(selectedNetwork) &&
|
|
||||||
<PasswordValidator
|
|
||||||
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 &&
|
|
||||||
<Fragment>
|
|
||||||
<TextValidator
|
|
||||||
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
|
|
||||||
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
|
|
||||||
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
|
|
||||||
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
|
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</Fragment>
|
|
||||||
}
|
|
||||||
|
|
||||||
<Button variant="contained" color="primary" className={classes.button} type="submit">
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
</ValidatorForm>
|
|
||||||
|
|
||||||
:
|
|
||||||
|
|
||||||
<div className={classes.loadingSettings}>
|
|
||||||
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
|
||||||
{errorMessage}
|
|
||||||
</Typography>
|
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
</div>
|
{
|
||||||
|
!isNetworkOpen(selectedNetwork) &&
|
||||||
|
<PasswordValidator
|
||||||
|
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 &&
|
||||||
|
<Fragment>
|
||||||
|
<TextValidator
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
<Button variant="contained" color="primary" className={classes.button} type="submit">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</ValidatorForm>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WiFiSettingsForm.propTypes = {
|
WiFiSettingsForm.propTypes = {
|
||||||
classes: PropTypes.object.isRequired,
|
classes: PropTypes.object.isRequired,
|
||||||
wifiSettingsFetched: PropTypes.bool.isRequired,
|
|
||||||
wifiSettings: PropTypes.object,
|
wifiSettings: PropTypes.object,
|
||||||
errorMessage: PropTypes.string,
|
|
||||||
deselectNetwork: PropTypes.func,
|
deselectNetwork: PropTypes.func,
|
||||||
selectedNetwork: PropTypes.object,
|
selectedNetwork: PropTypes.object,
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
|
82
interface/src/project/DemoController.js
Normal file
82
interface/src/project/DemoController.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { ValidatorForm } from 'react-material-ui-form-validator';
|
||||||
|
|
||||||
|
import { ENDPOINT_ROOT } from '../constants/Env';
|
||||||
|
import SectionContent from '../components/SectionContent';
|
||||||
|
import { restComponent } from '../components/RestComponent';
|
||||||
|
import LoadingNotification from '../components/LoadingNotification';
|
||||||
|
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import Slider from '@material-ui/core/Slider';
|
||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
export const DEMO_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "demoSettings";
|
||||||
|
|
||||||
|
const valueToPercentage = (value) => `${Math.round(value / 255 * 100)}%`;
|
||||||
|
|
||||||
|
class DemoController extends Component {
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { data, fetched, errorMessage, saveData, loadData, handleSliderChange } = this.props;
|
||||||
|
return (
|
||||||
|
<SectionContent title="Controller" titleGutter>
|
||||||
|
<LoadingNotification
|
||||||
|
onReset={loadData}
|
||||||
|
fetched={fetched}
|
||||||
|
errorMessage={errorMessage}
|
||||||
|
render={() =>
|
||||||
|
<DemoControllerForm
|
||||||
|
demoSettings={data}
|
||||||
|
onReset={loadData}
|
||||||
|
onSubmit={saveData}
|
||||||
|
handleSliderChange={handleSliderChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SectionContent>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles(theme => ({
|
||||||
|
button: {
|
||||||
|
marginRight: theme.spacing(2),
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
},
|
||||||
|
blinkSpeedLabel: {
|
||||||
|
marginBottom: theme.spacing(5),
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
function DemoControllerForm(props) {
|
||||||
|
const { demoSettings, onSubmit, onReset, handleSliderChange } = props;
|
||||||
|
const classes = useStyles();
|
||||||
|
return (
|
||||||
|
<ValidatorForm onSubmit={onSubmit}>
|
||||||
|
<Typography id="blink-speed-slider" className={classes.blinkSpeedLabel}>
|
||||||
|
Blink Speed
|
||||||
|
</Typography>
|
||||||
|
<Slider
|
||||||
|
value={demoSettings.blink_speed}
|
||||||
|
valueLabelFormat={valueToPercentage}
|
||||||
|
aria-labelledby="blink-speed-slider"
|
||||||
|
valueLabelDisplay="on"
|
||||||
|
min={0}
|
||||||
|
max={255}
|
||||||
|
onChange={handleSliderChange('blink_speed')}
|
||||||
|
/>
|
||||||
|
<Button variant="contained" color="primary" className={classes.button} type="submit">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</ValidatorForm>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default restComponent(DEMO_SETTINGS_ENDPOINT, DemoController);
|
100
interface/src/project/DemoInformation.js
Normal file
100
interface/src/project/DemoInformation.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
|
import Table from '@material-ui/core/Table';
|
||||||
|
import TableHead from '@material-ui/core/TableHead';
|
||||||
|
import TableCell from '@material-ui/core/TableCell';
|
||||||
|
import TableBody from '@material-ui/core/TableBody';
|
||||||
|
import TableRow from '@material-ui/core/TableRow';
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
|
||||||
|
import SectionContent from '../components/SectionContent';
|
||||||
|
|
||||||
|
const styles = theme => ({
|
||||||
|
fileTable: {
|
||||||
|
marginBottom: theme.spacing(2)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class DemoInformation extends Component {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { classes } = this.props;
|
||||||
|
return (
|
||||||
|
<SectionContent title="Demo Project - Blink Speed Controller" titleGutter>
|
||||||
|
<Typography variant="body1" paragraph>
|
||||||
|
This simple demo project allows you to control the blink speed of the built-in LED.
|
||||||
|
It demonstrates how the esp8266-react framework may be extended for your own IoT project.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" paragraph>
|
||||||
|
It is recommended that you keep your project interface code under the 'project' directory.
|
||||||
|
This serves to isolate your project code from the from the rest of the user interface which should
|
||||||
|
simplify merges should you wish to update your project with future framework changes.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" paragraph>
|
||||||
|
The demo project interface code stored in the interface/project directory:
|
||||||
|
</Typography>
|
||||||
|
<Table className={classes.fileTable}>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
File
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
Description
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
ProjectMenu.js
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
You can add your project's screens to the side bar here.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
ProjectRouting.js
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
The routing which controls the screens of your project.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
DemoProject.js
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
This screen, with tabs and tab routing.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
DemoInformation.js
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
The demo information tab.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
DemoController.js
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
The demo controller tab, to control the built-in LED.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<Typography variant="body1" paragraph>
|
||||||
|
See the project <a href="https://github.com/rjwats/esp8266-react/">README</a> for a full description of the demo project.
|
||||||
|
</Typography>
|
||||||
|
</SectionContent>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(DemoInformation);
|
37
interface/src/project/DemoProject.js
Normal file
37
interface/src/project/DemoProject.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Redirect, Switch } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { PROJECT_PATH } from '../constants/Env';
|
||||||
|
import MenuAppBar from '../components/MenuAppBar';
|
||||||
|
import AuthenticatedRoute from '../authentication/AuthenticatedRoute';
|
||||||
|
import DemoInformation from './DemoInformation';
|
||||||
|
import DemoController from './DemoController';
|
||||||
|
|
||||||
|
import Tabs from '@material-ui/core/Tabs';
|
||||||
|
import Tab from '@material-ui/core/Tab';
|
||||||
|
|
||||||
|
class DemoProject extends Component {
|
||||||
|
|
||||||
|
handleTabChange = (event, path) => {
|
||||||
|
this.props.history.push(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<MenuAppBar sectionTitle="Demo Project">
|
||||||
|
<Tabs value={this.props.match.url} onChange={this.handleTabChange} indicatorColor="primary" textColor="primary" variant="fullWidth">
|
||||||
|
<Tab value={`/${PROJECT_PATH}/demo/information`} label="Information" />
|
||||||
|
<Tab value={`/${PROJECT_PATH}/demo/controller`} label="Controller" />
|
||||||
|
</Tabs>
|
||||||
|
<Switch>
|
||||||
|
<AuthenticatedRoute exact path={`/${PROJECT_PATH}/demo/information`} component={DemoInformation} />
|
||||||
|
<AuthenticatedRoute exact path={`/${PROJECT_PATH}/demo/controller`} component={DemoController} />
|
||||||
|
<Redirect to={`/${PROJECT_PATH}/demo/information`} />
|
||||||
|
</Switch>
|
||||||
|
</MenuAppBar>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DemoProject;
|
30
interface/src/project/ProjectMenu.js
Normal file
30
interface/src/project/ProjectMenu.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Link, withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { PROJECT_PATH } from '../constants/Env';
|
||||||
|
|
||||||
|
import List from '@material-ui/core/List';
|
||||||
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
|
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||||
|
import ListItemText from '@material-ui/core/ListItemText';
|
||||||
|
import SettingsRemoteIcon from '@material-ui/icons/SettingsRemote';
|
||||||
|
|
||||||
|
class ProjectMenu extends Component {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const path = this.props.match.url;
|
||||||
|
return (
|
||||||
|
<List>
|
||||||
|
<ListItem to={`/${PROJECT_PATH}/demo/`} selected={path.startsWith(`/${PROJECT_PATH}/demo/`)} button component={Link}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<SettingsRemoteIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Demo Project" />
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withRouter(ProjectMenu);
|
32
interface/src/project/ProjectRouting.js
Normal file
32
interface/src/project/ProjectRouting.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Redirect, Switch } from 'react-router';
|
||||||
|
|
||||||
|
import { PROJECT_PATH } from '../constants/Env';
|
||||||
|
import AuthenticatedRoute from '../authentication/AuthenticatedRoute';
|
||||||
|
import DemoProject from './DemoProject';
|
||||||
|
|
||||||
|
class ProjectRouting extends Component {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Add your project page routing below.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
<AuthenticatedRoute exact path={`/${PROJECT_PATH}/demo/*`} component={DemoProject} />
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The redirect below caters for the default project route and redirecting invalid paths.
|
||||||
|
* The "to" property must match one of the routes above for this to work correctly.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
<Redirect to={`/${PROJECT_PATH}/demo/`} />
|
||||||
|
</Switch>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectRouting;
|
@ -32,6 +32,7 @@ class NetworkTime extends Component {
|
|||||||
</MenuAppBar>
|
</MenuAppBar>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withAuthenticationContext(NetworkTime)
|
export default withAuthenticationContext(NetworkTime)
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
#include <APSettingsService.h>
|
#include <APSettingsService.h>
|
||||||
|
|
||||||
APSettingsService::APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : AdminSettingsService(server, fs, securityManager, AP_SETTINGS_SERVICE_PATH, AP_SETTINGS_FILE) {
|
APSettingsService::APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : AdminSettingsService(server, fs, securityManager, AP_SETTINGS_SERVICE_PATH, AP_SETTINGS_FILE) {}
|
||||||
onConfigUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
APSettingsService::~APSettingsService() {}
|
APSettingsService::~APSettingsService() {}
|
||||||
|
|
||||||
|
void APSettingsService::begin() {
|
||||||
|
SettingsService::begin();
|
||||||
|
onConfigUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
void APSettingsService::loop() {
|
void APSettingsService::loop() {
|
||||||
unsigned long currentMillis = millis();
|
unsigned long currentMillis = millis();
|
||||||
unsigned long manageElapsed = (unsigned long)(currentMillis - _lastManaged);
|
unsigned long manageElapsed = (unsigned long)(currentMillis - _lastManaged);
|
||||||
@ -80,7 +83,4 @@ void APSettingsService::writeToJsonObject(JsonObject& root) {
|
|||||||
|
|
||||||
void APSettingsService::onConfigUpdated() {
|
void APSettingsService::onConfigUpdated() {
|
||||||
_lastManaged = millis() - MANAGE_NETWORK_DELAY;
|
_lastManaged = millis() - MANAGE_NETWORK_DELAY;
|
||||||
|
|
||||||
// stop softAP - forces reconfiguration in loop()
|
|
||||||
stopAP();
|
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
#ifndef APSettingsConfig_h
|
#ifndef APSettingsConfig_h
|
||||||
#define APSettingsConfig_h
|
#define APSettingsConfig_h
|
||||||
|
|
||||||
#include <SettingsService.h>
|
#include <AdminSettingsService.h>
|
||||||
#include <DNSServer.h>
|
#include <DNSServer.h>
|
||||||
#include <IPAddress.h>
|
#include <IPAddress.h>
|
||||||
|
|
||||||
@ -26,6 +26,7 @@ class APSettingsService : public AdminSettingsService {
|
|||||||
APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
|
APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
|
||||||
~APSettingsService();
|
~APSettingsService();
|
||||||
|
|
||||||
|
void begin();
|
||||||
void loop();
|
void loop();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -49,7 +50,7 @@ class APSettingsService : public AdminSettingsService {
|
|||||||
|
|
||||||
void manageAP();
|
void manageAP();
|
||||||
void startAP();
|
void startAP();
|
||||||
void stopAP();
|
void stopAP() ;
|
||||||
void handleDNS();
|
void handleDNS();
|
||||||
|
|
||||||
};
|
};
|
@ -1,8 +1,8 @@
|
|||||||
#include <APStatus.h>
|
#include <APStatus.h>
|
||||||
|
|
||||||
APStatus::APStatus(AsyncWebServer *server, SecurityManager* securityManager) : _server(server), _securityManager(securityManager) {
|
APStatus::APStatus(AsyncWebServer* server, SecurityManager* securityManager) {
|
||||||
_server->on(AP_STATUS_SERVICE_PATH, HTTP_GET,
|
server->on(AP_STATUS_SERVICE_PATH, HTTP_GET,
|
||||||
_securityManager->wrapRequest(std::bind(&APStatus::apStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)
|
securityManager->wrapRequest(std::bind(&APStatus::apStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -22,13 +22,10 @@ class APStatus {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
APStatus(AsyncWebServer *server, SecurityManager* securityManager);
|
APStatus(AsyncWebServer* server, SecurityManager* securityManager);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
AsyncWebServer* _server;
|
|
||||||
SecurityManager* _securityManager;
|
|
||||||
|
|
||||||
void apStatus(AsyncWebServerRequest *request);
|
void apStatus(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
};
|
};
|
45
lib/framework/AdminSettingsService.h
Normal file
45
lib/framework/AdminSettingsService.h
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#ifndef AdminSettingsService_h
|
||||||
|
#define AdminSettingsService_h
|
||||||
|
|
||||||
|
#include <SettingsService.h>
|
||||||
|
|
||||||
|
class AdminSettingsService : public SettingsService {
|
||||||
|
|
||||||
|
public:
|
||||||
|
AdminSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, char const* servicePath, char const* filePath):
|
||||||
|
SettingsService(server, fs, servicePath, filePath), _securityManager(securityManager) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// will validate the requests with the security manager
|
||||||
|
SecurityManager* _securityManager;
|
||||||
|
|
||||||
|
void fetchConfig(AsyncWebServerRequest *request) {
|
||||||
|
// verify the request against the predicate
|
||||||
|
Authentication authentication = _securityManager->authenticateRequest(request);
|
||||||
|
if (!getAuthenticationPredicate()(authentication)) {
|
||||||
|
request->send(401);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// delegate to underlying implemetation
|
||||||
|
SettingsService::fetchConfig(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateConfig(AsyncWebServerRequest *request, JsonDocument &jsonDocument) {
|
||||||
|
// verify the request against the predicate
|
||||||
|
Authentication authentication = _securityManager->authenticateRequest(request);
|
||||||
|
if (!getAuthenticationPredicate()(authentication)) {
|
||||||
|
request->send(401);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// delegate to underlying implemetation
|
||||||
|
SettingsService::updateConfig(request, jsonDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
// override this to replace the default authentication predicate, IS_ADMIN
|
||||||
|
AuthenticationPredicate getAuthenticationPredicate() {
|
||||||
|
return AuthenticationPredicates::IS_ADMIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // end AdminSettingsService
|
@ -1,7 +1,6 @@
|
|||||||
#include <AuthenticationService.h>
|
#include <AuthenticationService.h>
|
||||||
|
|
||||||
AuthenticationService::AuthenticationService(AsyncWebServer* server, SecurityManager* securityManager):
|
AuthenticationService::AuthenticationService(AsyncWebServer* server, SecurityManager* securityManager) : _securityManager(securityManager) {
|
||||||
_server(server), _securityManager(securityManager) {
|
|
||||||
server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, std::bind(&AuthenticationService::verifyAuthorization, this, std::placeholders::_1));
|
server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, std::bind(&AuthenticationService::verifyAuthorization, this, std::placeholders::_1));
|
||||||
|
|
||||||
_signInHandler.setUri(SIGN_IN_PATH);
|
_signInHandler.setUri(SIGN_IN_PATH);
|
@ -15,12 +15,11 @@ class AuthenticationService {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
AuthenticationService(AsyncWebServer* server, SecurityManager* securityManager) ;
|
AuthenticationService(AsyncWebServer* server, SecurityManager* securityManager);
|
||||||
~AuthenticationService();
|
~AuthenticationService();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// server instance
|
|
||||||
AsyncWebServer* _server;
|
|
||||||
SecurityManager* _securityManager;
|
SecurityManager* _securityManager;
|
||||||
AsyncJsonWebHandler _signInHandler;
|
AsyncJsonWebHandler _signInHandler;
|
||||||
|
|
55
lib/framework/ESP8266React.cpp
Normal file
55
lib/framework/ESP8266React.cpp
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#include <ESP8266React.h>
|
||||||
|
|
||||||
|
ESP8266React::ESP8266React(AsyncWebServer* server, FS* fs):
|
||||||
|
_securitySettingsService(server, fs),
|
||||||
|
_wifiSettingsService(server, fs, &_securitySettingsService),
|
||||||
|
_apSettingsService(server, fs, &_securitySettingsService),
|
||||||
|
_ntpSettingsService(server, fs, &_securitySettingsService),
|
||||||
|
_otaSettingsService(server, fs, &_securitySettingsService),
|
||||||
|
_authenticationService(server, &_securitySettingsService),
|
||||||
|
_wifiScanner(server, &_securitySettingsService),
|
||||||
|
_wifiStatus(server, &_securitySettingsService),
|
||||||
|
_ntpStatus(server, &_securitySettingsService),
|
||||||
|
_apStatus(server, &_securitySettingsService),
|
||||||
|
_systemStatus(server, &_securitySettingsService) {
|
||||||
|
// Serve static resources from /www/
|
||||||
|
server->serveStatic("/js/", SPIFFS, "/www/js/");
|
||||||
|
server->serveStatic("/css/", SPIFFS, "/www/css/");
|
||||||
|
server->serveStatic("/fonts/", SPIFFS, "/www/fonts/");
|
||||||
|
server->serveStatic("/app/", SPIFFS, "/www/app/");
|
||||||
|
server->serveStatic("/favicon.ico", SPIFFS, "/www/favicon.ico");
|
||||||
|
|
||||||
|
// Serving all other get requests with "/www/index.htm"
|
||||||
|
// OPTIONS get a straight up 200 response
|
||||||
|
server->onNotFound([](AsyncWebServerRequest *request) {
|
||||||
|
if (request->method() == HTTP_GET) {
|
||||||
|
request->send(SPIFFS, "/www/index.html");
|
||||||
|
} else if (request->method() == HTTP_OPTIONS) {
|
||||||
|
request->send(200);
|
||||||
|
} else {
|
||||||
|
request->send(404);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disable CORS if required
|
||||||
|
#if defined(ENABLE_CORS)
|
||||||
|
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", CORS_ORIGIN);
|
||||||
|
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
|
||||||
|
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Credentials", "true");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP8266React::begin() {
|
||||||
|
_securitySettingsService.begin();
|
||||||
|
_wifiSettingsService.begin();
|
||||||
|
_apSettingsService.begin();
|
||||||
|
_ntpSettingsService.begin();
|
||||||
|
_otaSettingsService.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP8266React::loop() {
|
||||||
|
_wifiSettingsService.loop();
|
||||||
|
_apSettingsService.loop();
|
||||||
|
_ntpSettingsService.loop();
|
||||||
|
_otaSettingsService.loop();
|
||||||
|
}
|
59
lib/framework/ESP8266React.h
Normal file
59
lib/framework/ESP8266React.h
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#ifndef ESP8266React_h
|
||||||
|
#define ESP8266React_h
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ESPAsyncTCP.h>
|
||||||
|
#elif defined(ESP_PLATFORM)
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <AsyncTCP.h>
|
||||||
|
#include <SPIFFS.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <FS.h>
|
||||||
|
#include <SecuritySettingsService.h>
|
||||||
|
#include <WiFiSettingsService.h>
|
||||||
|
#include <APSettingsService.h>
|
||||||
|
#include <NTPSettingsService.h>
|
||||||
|
#include <OTASettingsService.h>
|
||||||
|
#include <AuthenticationService.h>
|
||||||
|
#include <WiFiScanner.h>
|
||||||
|
#include <WiFiStatus.h>
|
||||||
|
#include <NTPStatus.h>
|
||||||
|
#include <APStatus.h>
|
||||||
|
#include <SystemStatus.h>
|
||||||
|
|
||||||
|
class ESP8266React {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
ESP8266React(AsyncWebServer* server, FS* fs);
|
||||||
|
|
||||||
|
void begin();
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
SecurityManager* getSecurityManager(){
|
||||||
|
return &_securitySettingsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
SecuritySettingsService _securitySettingsService;
|
||||||
|
|
||||||
|
WiFiSettingsService _wifiSettingsService;
|
||||||
|
APSettingsService _apSettingsService;
|
||||||
|
NTPSettingsService _ntpSettingsService;
|
||||||
|
OTASettingsService _otaSettingsService;
|
||||||
|
AuthenticationService _authenticationService;
|
||||||
|
|
||||||
|
WiFiScanner _wifiScanner;
|
||||||
|
WiFiStatus _wifiStatus;
|
||||||
|
NTPStatus _ntpStatus;
|
||||||
|
APStatus _apStatus;
|
||||||
|
SystemStatus _systemStatus;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -1,7 +1,7 @@
|
|||||||
#ifndef NTPSettingsService_h
|
#ifndef NTPSettingsService_h
|
||||||
#define NTPSettingsService_h
|
#define NTPSettingsService_h
|
||||||
|
|
||||||
#include <SettingsService.h>
|
#include <AdminSettingsService.h>
|
||||||
|
|
||||||
#include <TimeLib.h>
|
#include <TimeLib.h>
|
||||||
#include <NtpClientLib.h>
|
#include <NtpClientLib.h>
|
@ -1,8 +1,8 @@
|
|||||||
#include <NTPStatus.h>
|
#include <NTPStatus.h>
|
||||||
|
|
||||||
NTPStatus::NTPStatus(AsyncWebServer *server, SecurityManager* securityManager) : _server(server), _securityManager(securityManager) {
|
NTPStatus::NTPStatus(AsyncWebServer* server, SecurityManager* securityManager) {
|
||||||
_server->on(NTP_STATUS_SERVICE_PATH, HTTP_GET,
|
server->on(NTP_STATUS_SERVICE_PATH, HTTP_GET,
|
||||||
_securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)
|
securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -23,13 +23,10 @@ class NTPStatus {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
NTPStatus(AsyncWebServer *server, SecurityManager* securityManager);
|
NTPStatus(AsyncWebServer* server, SecurityManager* securityManager);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
AsyncWebServer* _server;
|
|
||||||
SecurityManager* _securityManager;
|
|
||||||
|
|
||||||
void ntpStatus(AsyncWebServerRequest *request);
|
void ntpStatus(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
};
|
};
|
@ -1,7 +1,7 @@
|
|||||||
#ifndef OTASettingsService_h
|
#ifndef OTASettingsService_h
|
||||||
#define OTASettingsService_h
|
#define OTASettingsService_h
|
||||||
|
|
||||||
#include <SettingsService.h>
|
#include <AdminSettingsService.h>
|
||||||
|
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
#include <ESP8266mDNS.h>
|
#include <ESP8266mDNS.h>
|
||||||
@ -52,4 +52,4 @@ class OTASettingsService : public AdminSettingsService {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // end NTPSettingsService_h
|
#endif // end OTASettingsService_h
|
@ -29,7 +29,3 @@ void SecuritySettingsService::writeToJsonObject(JsonObject& root) {
|
|||||||
user["admin"] = _user.isAdmin();
|
user["admin"] = _user.isAdmin();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SecuritySettingsService::begin() {
|
|
||||||
readFromFS();
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
#ifndef SecuritySettingsService_h
|
#ifndef SecuritySettingsService_h
|
||||||
#define SecuritySettingsService_h
|
#define SecuritySettingsService_h
|
||||||
|
|
||||||
#include <SettingsService.h>
|
#include <AdminSettingsService.h>
|
||||||
#include <SecurityManager.h>
|
#include <SecurityManager.h>
|
||||||
|
|
||||||
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
|
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
|
||||||
@ -14,8 +14,6 @@ class SecuritySettingsService : public AdminSettingsService, public SecurityMana
|
|||||||
SecuritySettingsService(AsyncWebServer* server, FS* fs);
|
SecuritySettingsService(AsyncWebServer* server, FS* fs);
|
||||||
~SecuritySettingsService();
|
~SecuritySettingsService();
|
||||||
|
|
||||||
void begin();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void readFromJsonObject(JsonObject& root);
|
void readFromJsonObject(JsonObject& root);
|
@ -47,7 +47,7 @@ protected:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void readFromFS(){
|
void readFromFS() {
|
||||||
File configFile = _fs->open(_filePath, "r");
|
File configFile = _fs->open(_filePath, "r");
|
||||||
|
|
||||||
// use defaults if no config found
|
// use defaults if no config found
|
@ -16,7 +16,6 @@
|
|||||||
#include <AsyncJsonWebHandler.h>
|
#include <AsyncJsonWebHandler.h>
|
||||||
#include <AsyncArduinoJson6.h>
|
#include <AsyncArduinoJson6.h>
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Abstraction of a service which stores it's settings as JSON in a file system.
|
* Abstraction of a service which stores it's settings as JSON in a file system.
|
||||||
*/
|
*/
|
||||||
@ -24,30 +23,25 @@ class SettingsService : public SettingsPersistence {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
SettingsService(AsyncWebServer* server, FS* fs, char const* servicePath, char const* filePath):
|
SettingsService(AsyncWebServer* server, FS* fs, char const* servicePath, char const* filePath): SettingsPersistence(fs, filePath), _servicePath(servicePath) {
|
||||||
SettingsPersistence(fs, filePath), _server(server) {
|
server->on(_servicePath, HTTP_GET, std::bind(&SettingsService::fetchConfig, this, std::placeholders::_1));
|
||||||
|
|
||||||
// configure fetch config handler
|
|
||||||
_server->on(servicePath, HTTP_GET, std::bind(&SettingsService::fetchConfig, this, std::placeholders::_1));
|
|
||||||
|
|
||||||
// configure update settings handler
|
|
||||||
_updateHandler.setUri(servicePath);
|
_updateHandler.setUri(servicePath);
|
||||||
_updateHandler.setMethod(HTTP_POST);
|
_updateHandler.setMethod(HTTP_POST);
|
||||||
_updateHandler.setMaxContentLength(MAX_SETTINGS_SIZE);
|
_updateHandler.setMaxContentLength(MAX_SETTINGS_SIZE);
|
||||||
_updateHandler.onRequest(std::bind(&SettingsService::updateConfig, this, std::placeholders::_1, std::placeholders::_2));
|
_updateHandler.onRequest(std::bind(&SettingsService::updateConfig, this, std::placeholders::_1, std::placeholders::_2));
|
||||||
_server->addHandler(&_updateHandler);
|
server->addHandler(&_updateHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~SettingsService() {}
|
virtual ~SettingsService() {}
|
||||||
|
|
||||||
virtual void begin() {
|
void begin() {
|
||||||
|
// read the initial data from the file system
|
||||||
readFromFS();
|
readFromFS();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// will serve setting endpoints from here
|
char const* _servicePath;
|
||||||
AsyncWebServer* _server;
|
|
||||||
|
|
||||||
AsyncJsonWebHandler _updateHandler;
|
AsyncJsonWebHandler _updateHandler;
|
||||||
|
|
||||||
virtual void fetchConfig(AsyncWebServerRequest *request) {
|
virtual void fetchConfig(AsyncWebServerRequest *request) {
|
||||||
@ -82,43 +76,4 @@ protected:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class AdminSettingsService : public SettingsService {
|
|
||||||
public:
|
|
||||||
AdminSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, char const* servicePath, char const* filePath):
|
|
||||||
SettingsService(server, fs, servicePath, filePath), _securityManager(securityManager) {
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// will validate the requests with the security manager
|
|
||||||
SecurityManager* _securityManager;
|
|
||||||
|
|
||||||
void fetchConfig(AsyncWebServerRequest *request) {
|
|
||||||
// verify the request against the predicate
|
|
||||||
Authentication authentication = _securityManager->authenticateRequest(request);
|
|
||||||
if (!getAuthenticationPredicate()(authentication)) {
|
|
||||||
request->send(401);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// delegate to underlying implemetation
|
|
||||||
SettingsService::fetchConfig(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateConfig(AsyncWebServerRequest *request, JsonDocument &jsonDocument) {
|
|
||||||
// verify the request against the predicate
|
|
||||||
Authentication authentication = _securityManager->authenticateRequest(request);
|
|
||||||
if (!getAuthenticationPredicate()(authentication)) {
|
|
||||||
request->send(401);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// delegate to underlying implemetation
|
|
||||||
SettingsService::updateConfig(request, jsonDocument);
|
|
||||||
}
|
|
||||||
|
|
||||||
// override to override the default authentication predicate, IS_ADMIN
|
|
||||||
AuthenticationPredicate getAuthenticationPredicate() {
|
|
||||||
return AuthenticationPredicates::IS_ADMIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // end SettingsService
|
#endif // end SettingsService
|
@ -33,7 +33,7 @@ private:
|
|||||||
|
|
||||||
AsyncJsonWebHandler _updateHandler;
|
AsyncJsonWebHandler _updateHandler;
|
||||||
|
|
||||||
void fetchConfig(AsyncWebServerRequest *request){
|
void fetchConfig(AsyncWebServerRequest *request) {
|
||||||
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_SETTINGS_SIZE);
|
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_SETTINGS_SIZE);
|
||||||
JsonObject jsonObject = response->getRoot();
|
JsonObject jsonObject = response->getRoot();
|
||||||
writeToJsonObject(jsonObject);
|
writeToJsonObject(jsonObject);
|
||||||
@ -41,8 +41,8 @@ private:
|
|||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateConfig(AsyncWebServerRequest *request, JsonDocument &jsonDocument){
|
void updateConfig(AsyncWebServerRequest *request, JsonDocument &jsonDocument) {
|
||||||
if (jsonDocument.is<JsonObject>()){
|
if (jsonDocument.is<JsonObject>()) {
|
||||||
JsonObject newConfig = jsonDocument.as<JsonObject>();
|
JsonObject newConfig = jsonDocument.as<JsonObject>();
|
||||||
readFromJsonObject(newConfig);
|
readFromJsonObject(newConfig);
|
||||||
|
|
||||||
@ -59,30 +59,23 @@ private:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
// will serve setting endpoints from here
|
|
||||||
AsyncWebServer* _server;
|
|
||||||
|
|
||||||
// reads the local config from the
|
// reads the local config from the
|
||||||
virtual void readFromJsonObject(JsonObject& root){}
|
virtual void readFromJsonObject(JsonObject& root) {}
|
||||||
virtual void writeToJsonObject(JsonObject& root){}
|
virtual void writeToJsonObject(JsonObject& root) {}
|
||||||
|
|
||||||
// implement to perform action when config has been updated
|
// implement to perform action when config has been updated
|
||||||
virtual void onConfigUpdated(){}
|
virtual void onConfigUpdated() {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
SimpleService(AsyncWebServer* server, char const* servicePath):
|
SimpleService(AsyncWebServer* server, char const* servicePath) {
|
||||||
_server(server) {
|
server->on(servicePath, HTTP_GET, std::bind(&SimpleService::fetchConfig, this, std::placeholders::_1));
|
||||||
|
|
||||||
// configure fetch config handler
|
|
||||||
_server->on(servicePath, HTTP_GET, std::bind(&SimpleService::fetchConfig, this, std::placeholders::_1));
|
|
||||||
|
|
||||||
// configure update settings handler
|
|
||||||
_updateHandler.setUri(servicePath);
|
_updateHandler.setUri(servicePath);
|
||||||
_updateHandler.setMethod(HTTP_POST);
|
_updateHandler.setMethod(HTTP_POST);
|
||||||
_updateHandler.setMaxContentLength(MAX_SETTINGS_SIZE);
|
_updateHandler.setMaxContentLength(MAX_SETTINGS_SIZE);
|
||||||
_updateHandler.onRequest(std::bind(&SimpleService::updateConfig, this, std::placeholders::_1, std::placeholders::_2));
|
_updateHandler.onRequest(std::bind(&SimpleService::updateConfig, this, std::placeholders::_1, std::placeholders::_2));
|
||||||
_server->addHandler(&_updateHandler);
|
server->addHandler(&_updateHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~SimpleService() {}
|
virtual ~SimpleService() {}
|
@ -1,12 +1,12 @@
|
|||||||
#include <SystemStatus.h>
|
#include <SystemStatus.h>
|
||||||
|
|
||||||
SystemStatus::SystemStatus(AsyncWebServer *server, SecurityManager* securityManager) : _server(server), _securityManager(securityManager) {
|
SystemStatus::SystemStatus(AsyncWebServer* server, SecurityManager* securityManager) {
|
||||||
_server->on(SYSTEM_STATUS_SERVICE_PATH, HTTP_GET,
|
server->on(SYSTEM_STATUS_SERVICE_PATH, HTTP_GET,
|
||||||
_securityManager->wrapRequest(std::bind(&SystemStatus::systemStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)
|
securityManager->wrapRequest(std::bind(&SystemStatus::systemStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SystemStatus::systemStatus(AsyncWebServerRequest *request) {
|
void SystemStatus::systemStatus(AsyncWebServerRequest *request) {
|
||||||
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_ESP_STATUS_SIZE);
|
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_ESP_STATUS_SIZE);
|
||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
@ -21,13 +21,10 @@ class SystemStatus {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
SystemStatus(AsyncWebServer *server, SecurityManager* securityManager);
|
SystemStatus(AsyncWebServer* server, SecurityManager* securityManager);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
AsyncWebServer* _server;
|
|
||||||
SecurityManager* _securityManager;
|
|
||||||
|
|
||||||
void systemStatus(AsyncWebServerRequest *request);
|
void systemStatus(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
};
|
};
|
@ -1,13 +1,13 @@
|
|||||||
#include <WiFiScanner.h>
|
#include <WiFiScanner.h>
|
||||||
|
|
||||||
WiFiScanner::WiFiScanner(AsyncWebServer *server, SecurityManager* securityManager) : _server(server) {
|
WiFiScanner::WiFiScanner(AsyncWebServer *server, SecurityManager* securityManager) {
|
||||||
_server->on(SCAN_NETWORKS_SERVICE_PATH, HTTP_GET,
|
server->on(SCAN_NETWORKS_SERVICE_PATH, HTTP_GET,
|
||||||
securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN)
|
securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN)
|
||||||
);
|
);
|
||||||
_server->on(LIST_NETWORKS_SERVICE_PATH, HTTP_GET,
|
server->on(LIST_NETWORKS_SERVICE_PATH, HTTP_GET,
|
||||||
securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN)
|
securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN)
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
void WiFiScanner::scanNetworks(AsyncWebServerRequest *request) {
|
void WiFiScanner::scanNetworks(AsyncWebServerRequest *request) {
|
||||||
if (WiFi.scanComplete() != -1){
|
if (WiFi.scanComplete() != -1){
|
@ -28,8 +28,6 @@ class WiFiScanner {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
AsyncWebServer* _server;
|
|
||||||
|
|
||||||
void scanNetworks(AsyncWebServerRequest *request);
|
void scanNetworks(AsyncWebServerRequest *request);
|
||||||
void listNetworks(AsyncWebServerRequest *request);
|
void listNetworks(AsyncWebServerRequest *request);
|
||||||
|
|
@ -1,9 +1,16 @@
|
|||||||
#include <WiFiSettingsService.h>
|
#include <WiFiSettingsService.h>
|
||||||
|
|
||||||
WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : AdminSettingsService(server, fs, securityManager, WIFI_SETTINGS_SERVICE_PATH, WIFI_SETTINGS_FILE) {
|
WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : AdminSettingsService(server, fs, securityManager, WIFI_SETTINGS_SERVICE_PATH, WIFI_SETTINGS_FILE) {
|
||||||
|
// Disable WiFi config persistance and auto reconnect
|
||||||
|
WiFi.persistent(false);
|
||||||
|
WiFi.setAutoReconnect(false);
|
||||||
|
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
_onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1));
|
_onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1));
|
||||||
#elif defined(ESP_PLATFORM)
|
#elif defined(ESP_PLATFORM)
|
||||||
|
// Init the wifi driver on ESP32
|
||||||
|
WiFi.mode(WIFI_MODE_MAX);
|
||||||
|
WiFi.mode(WIFI_MODE_NULL);
|
||||||
WiFi.onEvent(std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2), WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
|
WiFi.onEvent(std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2), WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
#ifndef WiFiSettingsService_h
|
#ifndef WiFiSettingsService_h
|
||||||
#define WiFiSettingsService_h
|
#define WiFiSettingsService_h
|
||||||
|
|
||||||
#include <SettingsService.h>
|
#include <AdminSettingsService.h>
|
||||||
#include <IPAddress.h>
|
#include <IPAddress.h>
|
||||||
|
|
||||||
#define WIFI_SETTINGS_FILE "/config/wifiSettings.json"
|
#define WIFI_SETTINGS_FILE "/config/wifiSettings.json"
|
@ -1,8 +1,8 @@
|
|||||||
#include <WiFiStatus.h>
|
#include <WiFiStatus.h>
|
||||||
|
|
||||||
WiFiStatus::WiFiStatus(AsyncWebServer *server, SecurityManager* securityManager) : _server(server), _securityManager(securityManager) {
|
WiFiStatus::WiFiStatus(AsyncWebServer* server, SecurityManager* securityManager) {
|
||||||
_server->on(WIFI_STATUS_SERVICE_PATH, HTTP_GET,
|
server->on(WIFI_STATUS_SERVICE_PATH, HTTP_GET,
|
||||||
_securityManager->wrapRequest(std::bind(&WiFiStatus::wifiStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)
|
securityManager->wrapRequest(std::bind(&WiFiStatus::wifiStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)
|
||||||
);
|
);
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
_onStationModeConnectedHandler = WiFi.onStationModeConnected(onStationModeConnected);
|
_onStationModeConnectedHandler = WiFi.onStationModeConnected(onStationModeConnected);
|
@ -22,13 +22,10 @@ class WiFiStatus {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
WiFiStatus(AsyncWebServer *server, SecurityManager* securityManager);
|
WiFiStatus(AsyncWebServer* server, SecurityManager* securityManager);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
AsyncWebServer* _server;
|
|
||||||
SecurityManager* _securityManager;
|
|
||||||
|
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
// handler refrences for logging important WiFi events over serial
|
// handler refrences for logging important WiFi events over serial
|
||||||
WiFiEventHandler _onStationModeConnectedHandler;
|
WiFiEventHandler _onStationModeConnectedHandler;
|
26
src/DemoProject.cpp
Normal file
26
src/DemoProject.cpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#include <DemoProject.h>
|
||||||
|
|
||||||
|
DemoProject::DemoProject(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : AdminSettingsService(server, fs, securityManager, DEMO_SETTINGS_PATH, DEMO_SETTINGS_FILE) {
|
||||||
|
pinMode(BLINK_LED, OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
DemoProject::~DemoProject() {}
|
||||||
|
|
||||||
|
void DemoProject::loop() {
|
||||||
|
unsigned delay = MAX_DELAY / 255 * (255 - _blinkSpeed);
|
||||||
|
unsigned long currentMillis = millis();
|
||||||
|
if (!_lastBlink || (unsigned long)(currentMillis - _lastBlink) >= delay) {
|
||||||
|
_lastBlink = currentMillis;
|
||||||
|
digitalWrite(BLINK_LED, !digitalRead(BLINK_LED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DemoProject::readFromJsonObject(JsonObject& root) {
|
||||||
|
_blinkSpeed = root["blink_speed"] | DEFAULT_BLINK_SPEED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DemoProject::writeToJsonObject(JsonObject& root) {
|
||||||
|
// connection settings
|
||||||
|
root["blink_speed"] = _blinkSpeed;
|
||||||
|
}
|
||||||
|
|
34
src/DemoProject.h
Normal file
34
src/DemoProject.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#ifndef DemoProject_h
|
||||||
|
#define DemoProject_h
|
||||||
|
|
||||||
|
#include <AdminSettingsService.h>
|
||||||
|
|
||||||
|
#define BLINK_LED 2
|
||||||
|
#define MAX_DELAY 1000
|
||||||
|
|
||||||
|
#define DEFAULT_BLINK_SPEED 100
|
||||||
|
#define DEMO_SETTINGS_FILE "/config/demoSettings.json"
|
||||||
|
#define DEMO_SETTINGS_PATH "/rest/demoSettings"
|
||||||
|
|
||||||
|
class DemoProject : public AdminSettingsService {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
DemoProject(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
|
||||||
|
~DemoProject();
|
||||||
|
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
unsigned long _lastBlink = 0;
|
||||||
|
uint8_t _blinkSpeed = 255;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void readFromJsonObject(JsonObject& root);
|
||||||
|
void writeToJsonObject(JsonObject& root);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
99
src/main.cpp
99
src/main.cpp
@ -1,99 +1,34 @@
|
|||||||
#include <Arduino.h>
|
#include <ESP8266React.h>
|
||||||
|
#include <DemoProject.h>
|
||||||
#if defined(ESP8266)
|
|
||||||
#include <ESP8266WiFi.h>
|
|
||||||
#include <ESPAsyncTCP.h>
|
|
||||||
#elif defined(ESP_PLATFORM)
|
|
||||||
#include <WiFi.h>
|
|
||||||
#include <AsyncTCP.h>
|
|
||||||
#include <SPIFFS.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <FS.h>
|
#include <FS.h>
|
||||||
#include <SecuritySettingsService.h>
|
|
||||||
#include <WiFiSettingsService.h>
|
|
||||||
#include <APSettingsService.h>
|
|
||||||
#include <NTPSettingsService.h>
|
|
||||||
#include <OTASettingsService.h>
|
|
||||||
#include <AuthenticationService.h>
|
|
||||||
#include <WiFiScanner.h>
|
|
||||||
#include <WiFiStatus.h>
|
|
||||||
#include <NTPStatus.h>
|
|
||||||
#include <APStatus.h>
|
|
||||||
#include <SystemStatus.h>
|
|
||||||
|
|
||||||
#define SERIAL_BAUD_RATE 115200
|
#define SERIAL_BAUD_RATE 115200
|
||||||
|
|
||||||
AsyncWebServer server(80);
|
AsyncWebServer server(80);
|
||||||
|
ESP8266React esp8266React(&server, &SPIFFS);
|
||||||
SecuritySettingsService securitySettingsService = SecuritySettingsService(&server, &SPIFFS);
|
DemoProject demoProject = DemoProject(&server, &SPIFFS, esp8266React.getSecurityManager());
|
||||||
WiFiSettingsService wifiSettingsService = WiFiSettingsService(&server, &SPIFFS, &securitySettingsService);
|
|
||||||
APSettingsService apSettingsService = APSettingsService(&server, &SPIFFS, &securitySettingsService);
|
|
||||||
NTPSettingsService ntpSettingsService = NTPSettingsService(&server, &SPIFFS, &securitySettingsService);
|
|
||||||
OTASettingsService otaSettingsService = OTASettingsService(&server, &SPIFFS, &securitySettingsService);
|
|
||||||
AuthenticationService authenticationService = AuthenticationService(&server, &securitySettingsService);
|
|
||||||
|
|
||||||
WiFiScanner wifiScanner = WiFiScanner(&server, &securitySettingsService);
|
|
||||||
WiFiStatus wifiStatus = WiFiStatus(&server, &securitySettingsService);
|
|
||||||
NTPStatus ntpStatus = NTPStatus(&server, &securitySettingsService);
|
|
||||||
APStatus apStatus = APStatus(&server, &securitySettingsService);
|
|
||||||
SystemStatus systemStatus = SystemStatus(&server, &securitySettingsService);;
|
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
// Disable wifi config persistance and auto reconnect
|
// start serial and filesystem
|
||||||
WiFi.persistent(false);
|
|
||||||
WiFi.setAutoReconnect(false);
|
|
||||||
|
|
||||||
#if defined(ESP_PLATFORM)
|
|
||||||
// Init the wifi driver on ESP32
|
|
||||||
WiFi.mode(WIFI_MODE_MAX);
|
|
||||||
WiFi.mode(WIFI_MODE_NULL);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Serial.begin(SERIAL_BAUD_RATE);
|
Serial.begin(SERIAL_BAUD_RATE);
|
||||||
|
|
||||||
|
// start the file system (must be done before starting the framework)
|
||||||
SPIFFS.begin();
|
SPIFFS.begin();
|
||||||
|
|
||||||
// Start security settings service first
|
// start the framework and demo project
|
||||||
securitySettingsService.begin();
|
esp8266React.begin();
|
||||||
|
|
||||||
// Start services
|
// start the demo project
|
||||||
ntpSettingsService.begin();
|
demoProject.begin();
|
||||||
otaSettingsService.begin();
|
|
||||||
apSettingsService.begin();
|
|
||||||
wifiSettingsService.begin();
|
|
||||||
|
|
||||||
// Serving static resources from /www/
|
|
||||||
server.serveStatic("/js/", SPIFFS, "/www/js/");
|
|
||||||
server.serveStatic("/css/", SPIFFS, "/www/css/");
|
|
||||||
server.serveStatic("/fonts/", SPIFFS, "/www/fonts/");
|
|
||||||
server.serveStatic("/app/", SPIFFS, "/www/app/");
|
|
||||||
server.serveStatic("/favicon.ico", SPIFFS, "/www/favicon.ico");
|
|
||||||
|
|
||||||
// Serving all other get requests with "/www/index.htm"
|
|
||||||
// OPTIONS get a straight up 200 response
|
|
||||||
server.onNotFound([](AsyncWebServerRequest *request) {
|
|
||||||
if (request->method() == HTTP_GET) {
|
|
||||||
request->send(SPIFFS, "/www/index.html");
|
|
||||||
} else if (request->method() == HTTP_OPTIONS) {
|
|
||||||
request->send(200);
|
|
||||||
} else {
|
|
||||||
request->send(404);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Disable CORS if required
|
|
||||||
#if defined(ENABLE_CORS)
|
|
||||||
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", CORS_ORIGIN);
|
|
||||||
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
|
|
||||||
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Credentials", "true");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
// start the server
|
||||||
server.begin();
|
server.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
wifiSettingsService.loop();
|
// run the framework's loop function
|
||||||
apSettingsService.loop();
|
esp8266React.loop();
|
||||||
ntpSettingsService.loop();
|
|
||||||
otaSettingsService.loop();
|
// run the demo project's loop function
|
||||||
|
demoProject.loop();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user