adding esp8266-react's latest NTP library

This commit is contained in:
proddy
2021-01-18 21:15:35 +01:00
parent 44045ae658
commit 94ac0d1418
19 changed files with 6115 additions and 3825 deletions

14
.gitignore vendored
View File

@@ -11,16 +11,18 @@ debug.log
# platformio # platformio
.pio .pio
pio_local.ini pio_local.ini
.VSCodeCounter/ /.VSCodeCounter
# OS specific # OS specific
.DS_Store .DS_Store
*Thumbs.db *Thumbs.db
# project specfic # project specfic
scripts/stackdmp.txt /scripts/stackdmp.txt
emsesp emsesp
data/www/ /data/www
lib/framework/WWWData.h /lib/framework/WWWData.h
interface/build/ /interface/build
interface/node_modules/ /interface/node_modules
/interface/.eslintcache

11809
interface/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,32 +3,31 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@material-ui/core": "^4.11.0", "@material-ui/core": "^4.11.2",
"@material-ui/icons": "^4.9.1", "@material-ui/icons": "^4.11.2",
"@types/jwt-decode": "^3.1.0",
"@types/lodash": "^4.14.165", "@types/lodash": "^4.14.165",
"@types/node": "^12.12.32", "@types/node": "^12.12.32",
"@types/react": "^16.9.56", "@types/react": "^17.0.0",
"@types/react-dom": "^16.9.9", "@types/react-dom": "^17.0.0",
"@types/react-material-ui-form-validator": "^2.1.0", "@types/react-material-ui-form-validator": "^2.1.0",
"@types/react-router": "^5.1.8", "@types/react-router": "^5.1.8",
"@types/react-router-dom": "^5.1.6", "@types/react-router-dom": "^5.1.6",
"compression-webpack-plugin": "^4.0.0", "compression-webpack-plugin": "^4.0.0",
"jwt-decode": "^3.1.1", "jwt-decode": "^3.1.2",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"mime-types": "^2.1.27", "mime-types": "^2.1.28",
"moment": "^2.29.1", "notistack": "^1.0.3",
"notistack": "^1.0.1", "parse-ms": "^2.1.0",
"react": "^16.14.0", "react": "^17.0.1",
"react-dom": "^16.14.0", "react-dom": "^17.0.1",
"react-dropzone": "^11.2.4", "react-dropzone": "^11.2.4",
"react-form-validator-core": "^1.0.0", "react-form-validator-core": "^1.0.0",
"react-material-ui-form-validator": "^2.1.1", "react-material-ui-form-validator": "^2.1.1",
"react-router": "^5.2.0", "react-router": "^5.2.0",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "3.4.4", "react-scripts": "4.0.1",
"sockette": "^2.0.6", "sockette": "^2.0.6",
"typescript": "^4.0.2", "typescript": "4.0.5",
"zlib": "^1.0.5" "zlib": "^1.0.5"
}, },
"scripts": { "scripts": {
@@ -52,6 +51,6 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"react-app-rewired": "^2.1.6" "react-app-rewired": "^2.1.8"
} }
} }

View File

@@ -14,7 +14,7 @@ type APSettingsFormProps = RestFormProps<APSettings>;
class APSettingsForm extends React.Component<APSettingsFormProps> { class APSettingsForm extends React.Component<APSettingsFormProps> {
componentWillMount() { componentDidMount() {
ValidatorForm.addValidationRule('isIP', isIP); ValidatorForm.addValidationRule('isIP', isIP);
} }

View File

@@ -3,7 +3,7 @@ import { Redirect, Route, RouteProps, RouteComponentProps } from "react-router-d
import { withSnackbar, WithSnackbarProps } from 'notistack'; import { withSnackbar, WithSnackbarProps } from 'notistack';
import * as Authentication from './Authentication'; import * as Authentication from './Authentication';
import { withAuthenticationContext, AuthenticationContextProps, AuthenticatedContext } from './AuthenticationContext'; import { withAuthenticationContext, AuthenticationContextProps, AuthenticatedContext, AuthenticatedContextValue } from './AuthenticationContext';
type ChildComponent = React.ComponentType<RouteComponentProps<any>> | React.ComponentType<any>; type ChildComponent = React.ComponentType<RouteComponentProps<any>> | React.ComponentType<any>;
@@ -21,7 +21,7 @@ export class AuthenticatedRoute extends React.Component<AuthenticatedRouteProps>
const renderComponent: RenderComponent = (props) => { const renderComponent: RenderComponent = (props) => {
if (authenticationContext.me) { if (authenticationContext.me) {
return ( return (
<AuthenticatedContext.Provider value={authenticationContext as AuthenticatedContext}> <AuthenticatedContext.Provider value={authenticationContext as AuthenticatedContextValue}>
<Component {...props} /> <Component {...props} />
</AuthenticatedContext.Provider> </AuthenticatedContext.Provider>
); );

View File

@@ -6,20 +6,20 @@ export interface Me {
version: string; // proddy added version: string; // proddy added
} }
export interface AuthenticationContext { export interface AuthenticationContextValue {
refresh: () => void; refresh: () => void;
signIn: (accessToken: string) => void; signIn: (accessToken: string) => void;
signOut: () => void; signOut: () => void;
me?: Me; me?: Me;
} }
const AuthenticationContextDefaultValue = {} as AuthenticationContext const AuthenticationContextDefaultValue = {} as AuthenticationContextValue
export const AuthenticationContext = React.createContext( export const AuthenticationContext = React.createContext(
AuthenticationContextDefaultValue AuthenticationContextDefaultValue
); );
export interface AuthenticationContextProps { export interface AuthenticationContextProps {
authenticationContext: AuthenticationContext; authenticationContext: AuthenticationContextValue;
} }
export function withAuthenticationContext<T extends AuthenticationContextProps>(Component: React.ComponentType<T>) { export function withAuthenticationContext<T extends AuthenticationContextProps>(Component: React.ComponentType<T>) {
@@ -34,17 +34,17 @@ export function withAuthenticationContext<T extends AuthenticationContextProps>(
}; };
} }
export interface AuthenticatedContext extends AuthenticationContext { export interface AuthenticatedContextValue extends AuthenticationContextValue {
me: Me; me: Me;
} }
const AuthenticatedContextDefaultValue = {} as AuthenticatedContext const AuthenticatedContextDefaultValue = {} as AuthenticatedContextValue
export const AuthenticatedContext = React.createContext( export const AuthenticatedContext = React.createContext(
AuthenticatedContextDefaultValue AuthenticatedContextDefaultValue
); );
export interface AuthenticatedContextProps { export interface AuthenticatedContextProps {
authenticatedContext: AuthenticatedContext; authenticatedContext: AuthenticatedContextValue;
} }
export function withAuthenticatedContext<T extends AuthenticatedContextProps>(Component: React.ComponentType<T>) { export function withAuthenticatedContext<T extends AuthenticatedContextProps>(Component: React.ComponentType<T>) {

View File

@@ -5,14 +5,14 @@ import jwtDecode from 'jwt-decode';
import history from '../history' import history from '../history'
import { VERIFY_AUTHORIZATION_ENDPOINT } from '../api'; import { VERIFY_AUTHORIZATION_ENDPOINT } from '../api';
import { ACCESS_TOKEN, authorizedFetch, getStorage } from './Authentication'; import { ACCESS_TOKEN, authorizedFetch, getStorage } from './Authentication';
import { AuthenticationContext, Me } from './AuthenticationContext'; import { AuthenticationContext, AuthenticationContextValue, Me } from './AuthenticationContext';
import FullScreenLoading from '../components/FullScreenLoading'; import FullScreenLoading from '../components/FullScreenLoading';
import { withFeatures, WithFeaturesProps } from '../features/FeaturesContext'; import { withFeatures, WithFeaturesProps } from '../features/FeaturesContext';
export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken) as Me; export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken) as Me;
interface AuthenticationWrapperState { interface AuthenticationWrapperState {
context: AuthenticationContext; context: AuthenticationContextValue;
initialized: boolean; initialized: boolean;
} }

View File

@@ -1,23 +0,0 @@
import React from 'react';
import { Features } from './types';
export interface ApplicationContext {
features: Features;
}
const ApplicationContextDefaultValue = {} as ApplicationContext
export const ApplicationContext = React.createContext(
ApplicationContextDefaultValue
);
export function withAuthenticatedContexApplicationContext<T extends ApplicationContext>(Component: React.ComponentType<T>) {
return class extends React.Component<Omit<T, keyof ApplicationContext>> {
render() {
return (
<ApplicationContext.Consumer>
{authenticatedContext => <Component {...this.props as T} features={authenticatedContext} />}
</ApplicationContext.Consumer>
);
}
};
}

View File

@@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import { Features } from './types'; import { Features } from './types';
export interface FeaturesContext { export interface FeaturesContextValue {
features: Features; features: Features;
} }
const FeaturesContextDefaultValue = {} as FeaturesContext const FeaturesContextDefaultValue = {} as FeaturesContextValue
export const FeaturesContext = React.createContext( export const FeaturesContext = React.createContext(
FeaturesContextDefaultValue FeaturesContextDefaultValue
); );

View File

@@ -9,12 +9,13 @@ import { MenuAppBar } from '../components';
import NetworkStatusController from './NetworkStatusController'; import NetworkStatusController from './NetworkStatusController';
import NetworkSettingsController from './NetworkSettingsController'; import NetworkSettingsController from './NetworkSettingsController';
import WiFiNetworkScanner from './WiFiNetworkScanner'; import WiFiNetworkScanner from './WiFiNetworkScanner';
import { NetworkConnectionContext } from './NetworkConnectionContext'; import { NetworkConnectionContext, NetworkConnectionContextValue } from './NetworkConnectionContext';
import { WiFiNetwork } from './types'; import { WiFiNetwork } from './types';
type NetworkConnectionProps = AuthenticatedContextProps & RouteComponentProps; type NetworkConnectionProps = AuthenticatedContextProps & RouteComponentProps;
class NetworkConnection extends Component<NetworkConnectionProps, NetworkConnectionContext> { class NetworkConnection extends Component<NetworkConnectionProps, NetworkConnectionContextValue> {
constructor(props: NetworkConnectionProps) { constructor(props: NetworkConnectionProps) {
super(props); super(props);

View File

@@ -1,13 +1,13 @@
import React from 'react'; import React from 'react';
import { WiFiNetwork } from './types'; import { WiFiNetwork } from './types';
export interface NetworkConnectionContext { export interface NetworkConnectionContextValue {
selectedNetwork?: WiFiNetwork; selectedNetwork?: WiFiNetwork;
selectNetwork: (network: WiFiNetwork) => void; selectNetwork: (network: WiFiNetwork) => void;
deselectNetwork: () => void; deselectNetwork: () => void;
} }
const NetworkConnectionContextDefaultValue = {} as NetworkConnectionContext const NetworkConnectionContextDefaultValue = {} as NetworkConnectionContextValue
export const NetworkConnectionContext = React.createContext( export const NetworkConnectionContext = React.createContext(
NetworkConnectionContextDefaultValue NetworkConnectionContextDefaultValue
); );

View File

@@ -14,7 +14,7 @@ import MenuItem from '@material-ui/core/MenuItem';
import { RestFormProps, PasswordValidator, BlockFormControlLabel, FormActions, FormButton } from '../components'; import { RestFormProps, PasswordValidator, BlockFormControlLabel, FormActions, FormButton } from '../components';
import { isIP, isHostname, optional } from '../validators'; import { isIP, isHostname, optional } from '../validators';
import { NetworkConnectionContext } from './NetworkConnectionContext'; import { NetworkConnectionContext, NetworkConnectionContextValue } from './NetworkConnectionContext';
import { isNetworkOpen, networkSecurityMode } from './WiFiSecurityModes'; import { isNetworkOpen, networkSecurityMode } from './WiFiSecurityModes';
import { NetworkSettings } from './types'; import { NetworkSettings } from './types';
@@ -25,7 +25,7 @@ class NetworkSettingsForm extends React.Component<NetworkStatusFormProps> {
static contextType = NetworkConnectionContext; static contextType = NetworkConnectionContext;
context!: React.ContextType<typeof NetworkConnectionContext>; context!: React.ContextType<typeof NetworkConnectionContext>;
constructor(props: NetworkStatusFormProps, context: NetworkConnectionContext) { constructor(props: NetworkStatusFormProps, context: NetworkConnectionContextValue) {
super(props); super(props);
const { selectedNetwork } = context; const { selectedNetwork } = context;

View File

@@ -1,5 +1,4 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import moment from 'moment';
import { WithTheme, withTheme } from '@material-ui/core/styles'; import { WithTheme, withTheme } from '@material-ui/core/styles';
import { Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText, Button } from '@material-ui/core'; import { Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText, Button } from '@material-ui/core';
@@ -14,7 +13,7 @@ import RefreshIcon from '@material-ui/icons/Refresh';
import { RestFormProps, FormButton, HighlightAvatar } from '../components'; import { RestFormProps, FormButton, HighlightAvatar } from '../components';
import { isNtpActive, ntpStatusHighlight, ntpStatus } from './NTPStatus'; import { isNtpActive, ntpStatusHighlight, ntpStatus } from './NTPStatus';
import { formatIsoDateTime, formatLocalDateTime } from './TimeFormat'; import { formatDuration, formatDateTime, formatLocalDateTimeNow, formatLocalDateTime } from './TimeFormat';
import { NTPStatus, Time } from './types'; import { NTPStatus, Time } from './types';
import { redirectingAuthorizedFetch, withAuthenticatedContext, AuthenticatedContextProps } from '../authentication'; import { redirectingAuthorizedFetch, withAuthenticatedContext, AuthenticatedContextProps } from '../authentication';
import { TIME_ENDPOINT } from '../api'; import { TIME_ENDPOINT } from '../api';
@@ -43,30 +42,21 @@ class NTPStatusForm extends Component<NTPStatusFormProps, NTPStatusFormState> {
} }
openSetTime = () => { openSetTime = () => {
this.setState({ localTime: formatLocalDateTime(moment()), settingTime: true, }); this.setState({ localTime: formatLocalDateTimeNow(), settingTime: true, });
} }
closeSetTime = () => { closeSetTime = () => {
this.setState({ settingTime: false }); this.setState({ settingTime: false });
} }
createAdjustedTime = (): Time => { createTime = (): Time => ({ local_time: formatLocalDateTime(this.state.localTime) });
const currentLocalTime = moment(this.props.data.time_local);
const newLocalTime = moment(this.state.localTime);
newLocalTime.subtract(currentLocalTime.utcOffset())
newLocalTime.milliseconds(0);
newLocalTime.utc();
return {
time_utc: newLocalTime.format()
}
}
configureTime = () => { configureTime = () => {
this.setState({ processing: true }); this.setState({ processing: true });
redirectingAuthorizedFetch(TIME_ENDPOINT, redirectingAuthorizedFetch(TIME_ENDPOINT,
{ {
method: 'POST', method: 'POST',
body: JSON.stringify(this.createAdjustedTime()), body: JSON.stringify(this.createTime()),
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
@@ -153,7 +143,7 @@ class NTPStatusForm extends Component<NTPStatusFormProps, NTPStatusFormState> {
<AccessTimeIcon /> <AccessTimeIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Local Time" secondary={formatIsoDateTime(data.time_local)} /> <ListItemText primary="Local Time" secondary={formatDateTime(data.local_time)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -162,7 +152,7 @@ class NTPStatusForm extends Component<NTPStatusFormProps, NTPStatusFormState> {
<SwapVerticalCircleIcon /> <SwapVerticalCircleIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="UTC Time" secondary={formatIsoDateTime(data.time_utc)} /> <ListItemText primary="UTC Time" secondary={formatDateTime(data.utc_time)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -171,7 +161,7 @@ class NTPStatusForm extends Component<NTPStatusFormProps, NTPStatusFormState> {
<AvTimerIcon /> <AvTimerIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Uptime" secondary={moment.duration(data.uptime, 'seconds').humanize()} /> <ListItemText primary="Uptime" secondary={formatDuration(data.uptime)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
</List> </List>

View File

@@ -1,5 +1,45 @@
import moment, { Moment } from 'moment'; import parseMilliseconds from 'parse-ms';
export const formatIsoDateTime = (isoDateString: string) => moment.parseZone(isoDateString).format('ll @ HH:mm:ss'); const LOCALE_FORMAT = new Intl.DateTimeFormat('default', {
day: 'numeric',
month: 'short',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
hour12: false
});
export const formatLocalDateTime = (moment: Moment) => moment.format('YYYY-MM-DDTHH:mm'); export const formatDateTime = (dateTime: string) => {
return LOCALE_FORMAT.format(new Date(dateTime.substr(0, 19)));
}
export const formatLocalDateTimeNow = () => {
return formatIsoDateTime(new Date()).substr(0, 19);
}
export const formatLocalDateTime = (dateTime: string) => {
return formatIsoDateTime(new Date(dateTime)).substr(0, 19);
}
export const formatIsoDateTime = (date: Date) => {
return new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().slice(0, -1);
}
export const formatDuration = (duration: number) => {
const { days, hours, minutes, seconds } = parseMilliseconds(duration * 1000);
var formatted = '';
if (days) {
formatted += days + ' days ';
}
if (formatted || hours) {
formatted += hours + ' hours ';
}
if (formatted || minutes) {
formatted += minutes + ' minutes ';
}
if (formatted || seconds) {
formatted += seconds + ' seconds';
}
return formatted;
}

View File

@@ -1,3 +1,4 @@
export enum NTPSyncStatus { export enum NTPSyncStatus {
NTP_INACTIVE = 0, NTP_INACTIVE = 0,
NTP_ACTIVE = 1 NTP_ACTIVE = 1
@@ -5,8 +6,8 @@ export enum NTPSyncStatus {
export interface NTPStatus { export interface NTPStatus {
status: NTPSyncStatus; status: NTPSyncStatus;
time_utc: string; utc_time: string;
time_local: string; local_time: string;
server: string; server: string;
uptime: number; uptime: number;
} }
@@ -19,5 +20,5 @@ export interface NTPSettings {
} }
export interface Time { export interface Time {
time_utc: string; local_time: string;
} }

View File

@@ -1 +1,3 @@
export default (validator: (value: any) => boolean) => (value: any) => !value || validator(value); const OPTIONAL = (validator: (value: any) => boolean) => (value: any) => !value || validator(value);
export default OPTIONAL;

View File

@@ -1,3 +1,6 @@
export default (validator1: (value: any) => boolean, validator2: (value: any) => boolean) => { const OR = (validator1: (value: any) => boolean, validator2: (value: any) => boolean) => {
return (value: any) => validator1(value) || validator2(value); return (value: any) => validator1(value) || validator2(value);
} };
export default OR;

View File

@@ -17,7 +17,8 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react" "jsx": "react",
"noFallthroughCasesInSwitch": true
}, },
"include": [ "include": [
"src" "src"

View File

@@ -1,17 +1,28 @@
#include <NTPStatus.h> #include <NTPStatus.h>
NTPStatus::NTPStatus(AsyncWebServer * server, SecurityManager * securityManager) { NTPStatus::NTPStatus(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(NTP_STATUS_SERVICE_PATH, server->on(NTP_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED));
HTTP_GET,
securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED));
} }
String toISOString(tm * time, bool incOffset) { /*
* Formats the time using the format provided.
*
* Uses a 25 byte buffer, large enough to fit an ISO time string with offset.
*/
String formatTime(tm * time, const char * format) {
char time_string[25]; char time_string[25];
strftime(time_string, 25, incOffset ? "%FT%T%z" : "%FT%TZ", time); strftime(time_string, 25, format, time);
return String(time_string); return String(time_string);
} }
String toUTCTimeString(tm * time) {
return formatTime(time, "%FT%TZ");
}
String toLocalTimeString(tm * time) {
return formatTime(time, "%FT%T");
}
void NTPStatus::ntpStatus(AsyncWebServerRequest * request) { void NTPStatus::ntpStatus(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_NTP_STATUS_SIZE); AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_NTP_STATUS_SIZE);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
@@ -23,10 +34,10 @@ void NTPStatus::ntpStatus(AsyncWebServerRequest * request) {
root["status"] = sntp_enabled() ? 1 : 0; root["status"] = sntp_enabled() ? 1 : 0;
// the current time in UTC // the current time in UTC
root["time_utc"] = toISOString(gmtime(&now), false); root["utc_time"] = toUTCTimeString(gmtime(&now));
// local time as ISO String with TZ // local time as ISO String with TZ
root["time_local"] = toISOString(localtime(&now), true); root["local_time"] = toLocalTimeString(localtime(&now));
// the sntp server name // the sntp server name
root["server"] = sntp_getservername(0); root["server"] = sntp_getservername(0);