Allow time & date to be configured manually when NTP is inactive (#153)
* Allow time to be configured manually when NTP is not active * Standarize on primary button on the outside of dialog boxes
This commit is contained in:
		@@ -3,6 +3,7 @@ import { ENDPOINT_ROOT } from './Env';
 | 
				
			|||||||
export const FEATURES_ENDPOINT = ENDPOINT_ROOT + "features";
 | 
					export const FEATURES_ENDPOINT = ENDPOINT_ROOT + "features";
 | 
				
			||||||
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";
 | 
				
			||||||
 | 
					export const TIME_ENDPOINT = ENDPOINT_ROOT + "time";
 | 
				
			||||||
export const AP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "apSettings";
 | 
					export const AP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "apSettings";
 | 
				
			||||||
export const AP_STATUS_ENDPOINT = ENDPOINT_ROOT + "apStatus";
 | 
					export const AP_STATUS_ENDPOINT = ENDPOINT_ROOT + "apStatus";
 | 
				
			||||||
export const SCAN_NETWORKS_ENDPOINT = ENDPOINT_ROOT + "scanNetworks";
 | 
					export const SCAN_NETWORKS_ENDPOINT = ENDPOINT_ROOT + "scanNetworks";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,8 @@ import React, { Component, Fragment } from 'react';
 | 
				
			|||||||
import moment from 'moment';
 | 
					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 } from '@material-ui/core';
 | 
					import { Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText, Button } from '@material-ui/core';
 | 
				
			||||||
 | 
					import { Dialog, DialogTitle, DialogContent, DialogActions, Box, TextField } from '@material-ui/core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import SwapVerticalCircleIcon from '@material-ui/icons/SwapVerticalCircle';
 | 
					import SwapVerticalCircleIcon from '@material-ui/icons/SwapVerticalCircle';
 | 
				
			||||||
import AccessTimeIcon from '@material-ui/icons/AccessTime';
 | 
					import AccessTimeIcon from '@material-ui/icons/AccessTime';
 | 
				
			||||||
@@ -11,18 +12,116 @@ import UpdateIcon from '@material-ui/icons/Update';
 | 
				
			|||||||
import AvTimerIcon from '@material-ui/icons/AvTimer';
 | 
					import AvTimerIcon from '@material-ui/icons/AvTimer';
 | 
				
			||||||
import RefreshIcon from '@material-ui/icons/Refresh';
 | 
					import RefreshIcon from '@material-ui/icons/Refresh';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { RestFormProps, FormActions, FormButton, HighlightAvatar } from '../components';
 | 
					import { RestFormProps, FormButton, HighlightAvatar } from '../components';
 | 
				
			||||||
 | 
					 | 
				
			||||||
import { isNtpActive, ntpStatusHighlight, ntpStatus } from './NTPStatus';
 | 
					import { isNtpActive, ntpStatusHighlight, ntpStatus } from './NTPStatus';
 | 
				
			||||||
import { formatIsoDateTime } from './TimeFormat';
 | 
					import { formatIsoDateTime, formatLocalDateTime } from './TimeFormat';
 | 
				
			||||||
import { NTPStatus } from './types';
 | 
					import { NTPStatus, Time } from './types';
 | 
				
			||||||
 | 
					import { redirectingAuthorizedFetch, withAuthenticatedContext, AuthenticatedContextProps } from '../authentication';
 | 
				
			||||||
 | 
					import { TIME_ENDPOINT } from '../api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type NTPStatusFormProps = RestFormProps<NTPStatus> & WithTheme;
 | 
					type NTPStatusFormProps = RestFormProps<NTPStatus> & WithTheme & AuthenticatedContextProps;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NTPStatusForm extends Component<NTPStatusFormProps> {
 | 
					interface NTPStatusFormState {
 | 
				
			||||||
 | 
					  settingTime: boolean;
 | 
				
			||||||
 | 
					  localTime: string;
 | 
				
			||||||
 | 
					  processing: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NTPStatusForm extends Component<NTPStatusFormProps, NTPStatusFormState> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: NTPStatusFormProps) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.state = {
 | 
				
			||||||
 | 
					      settingTime: false,
 | 
				
			||||||
 | 
					      localTime: '',
 | 
				
			||||||
 | 
					      processing: false
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) => {
 | 
				
			||||||
 | 
					    this.setState({ localTime: event.target.value });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  openSetTime = () => {
 | 
				
			||||||
 | 
					    this.setState({ localTime: formatLocalDateTime(moment()), settingTime: true, });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  closeSetTime = () => {
 | 
				
			||||||
 | 
					    this.setState({ settingTime: false });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  createAdjustedTime = (): Time => {
 | 
				
			||||||
 | 
					    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 = () => {
 | 
				
			||||||
 | 
					    this.setState({ processing: true });
 | 
				
			||||||
 | 
					    redirectingAuthorizedFetch(TIME_ENDPOINT,
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        method: 'POST',
 | 
				
			||||||
 | 
					        body: JSON.stringify(this.createAdjustedTime()),
 | 
				
			||||||
 | 
					        headers: {
 | 
				
			||||||
 | 
					          'Content-Type': 'application/json'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .then(response => {
 | 
				
			||||||
 | 
					        if (response.status === 200) {
 | 
				
			||||||
 | 
					          this.props.enqueueSnackbar("Time set successfully", { variant: 'success' });
 | 
				
			||||||
 | 
					          this.setState({ processing: false, settingTime: false }, this.props.loadData);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          throw Error("Error setting time, status code: " + response.status);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch(error => {
 | 
				
			||||||
 | 
					        this.props.enqueueSnackbar(error.message || "Problem setting the time", { variant: 'error' });
 | 
				
			||||||
 | 
					        this.setState({ processing: false, settingTime: false });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  renderSetTimeDialog() {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <Dialog
 | 
				
			||||||
 | 
					        open={this.state.settingTime}
 | 
				
			||||||
 | 
					        onClose={this.closeSetTime}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <DialogTitle>Set Time</DialogTitle>
 | 
				
			||||||
 | 
					        <DialogContent dividers>
 | 
				
			||||||
 | 
					          <Box mb={2}>Enter local date and time below to set the device's time.</Box>
 | 
				
			||||||
 | 
					          <TextField
 | 
				
			||||||
 | 
					            label="Local Time"
 | 
				
			||||||
 | 
					            type="datetime-local"
 | 
				
			||||||
 | 
					            value={this.state.localTime}
 | 
				
			||||||
 | 
					            onChange={this.updateLocalTime}
 | 
				
			||||||
 | 
					            disabled={this.state.processing}
 | 
				
			||||||
 | 
					            variant="outlined"
 | 
				
			||||||
 | 
					            fullWidth
 | 
				
			||||||
 | 
					            InputLabelProps={{
 | 
				
			||||||
 | 
					              shrink: true,
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </DialogContent>
 | 
				
			||||||
 | 
					        <DialogActions>
 | 
				
			||||||
 | 
					          <Button variant="contained" onClick={this.closeSetTime} color="secondary">
 | 
				
			||||||
 | 
					            Cancel
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
 | 
					          <Button startIcon={<AccessTimeIcon />} variant="contained" onClick={this.configureTime} disabled={this.state.processing} color="primary" autoFocus>
 | 
				
			||||||
 | 
					            Set Time
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
 | 
					        </DialogActions>
 | 
				
			||||||
 | 
					      </Dialog>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render() {
 | 
					  render() {
 | 
				
			||||||
    const { data, theme } = this.props
 | 
					    const { data, theme } = this.props
 | 
				
			||||||
 | 
					    const me = this.props.authenticatedContext.me;
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <Fragment>
 | 
					      <Fragment>
 | 
				
			||||||
        <List>
 | 
					        <List>
 | 
				
			||||||
@@ -40,19 +139,10 @@ class NTPStatusForm extends Component<NTPStatusFormProps> {
 | 
				
			|||||||
              <ListItem>
 | 
					              <ListItem>
 | 
				
			||||||
                <ListItemAvatar>
 | 
					                <ListItemAvatar>
 | 
				
			||||||
                  <Avatar>
 | 
					                  <Avatar>
 | 
				
			||||||
                    <AccessTimeIcon />
 | 
					                    <DNSIcon />
 | 
				
			||||||
                  </Avatar>
 | 
					                  </Avatar>
 | 
				
			||||||
                </ListItemAvatar>
 | 
					                </ListItemAvatar>
 | 
				
			||||||
                <ListItemText primary="Local Time" secondary={formatIsoDateTime(data.time_local)} />
 | 
					                <ListItemText primary="NTP Server" secondary={data.server} />
 | 
				
			||||||
              </ListItem>
 | 
					 | 
				
			||||||
              <Divider variant="inset" component="li" />
 | 
					 | 
				
			||||||
              <ListItem>
 | 
					 | 
				
			||||||
                <ListItemAvatar>
 | 
					 | 
				
			||||||
                  <Avatar>
 | 
					 | 
				
			||||||
                    <SwapVerticalCircleIcon />
 | 
					 | 
				
			||||||
                  </Avatar>
 | 
					 | 
				
			||||||
                </ListItemAvatar>
 | 
					 | 
				
			||||||
                <ListItemText primary="UTC Time" secondary={formatIsoDateTime(data.time_utc)} />
 | 
					 | 
				
			||||||
              </ListItem>
 | 
					              </ListItem>
 | 
				
			||||||
              <Divider variant="inset" component="li" />
 | 
					              <Divider variant="inset" component="li" />
 | 
				
			||||||
            </Fragment>
 | 
					            </Fragment>
 | 
				
			||||||
@@ -60,10 +150,19 @@ class NTPStatusForm extends Component<NTPStatusFormProps> {
 | 
				
			|||||||
          <ListItem>
 | 
					          <ListItem>
 | 
				
			||||||
            <ListItemAvatar>
 | 
					            <ListItemAvatar>
 | 
				
			||||||
              <Avatar>
 | 
					              <Avatar>
 | 
				
			||||||
                <DNSIcon />
 | 
					                <AccessTimeIcon />
 | 
				
			||||||
              </Avatar>
 | 
					              </Avatar>
 | 
				
			||||||
            </ListItemAvatar>
 | 
					            </ListItemAvatar>
 | 
				
			||||||
            <ListItemText primary="NTP Server" secondary={data.server} />
 | 
					            <ListItemText primary="Local Time" secondary={formatIsoDateTime(data.time_local)} />
 | 
				
			||||||
 | 
					          </ListItem>
 | 
				
			||||||
 | 
					          <Divider variant="inset" component="li" />
 | 
				
			||||||
 | 
					          <ListItem>
 | 
				
			||||||
 | 
					            <ListItemAvatar>
 | 
				
			||||||
 | 
					              <Avatar>
 | 
				
			||||||
 | 
					                <SwapVerticalCircleIcon />
 | 
				
			||||||
 | 
					              </Avatar>
 | 
				
			||||||
 | 
					            </ListItemAvatar>
 | 
				
			||||||
 | 
					            <ListItemText primary="UTC Time" secondary={formatIsoDateTime(data.time_utc)} />
 | 
				
			||||||
          </ListItem>
 | 
					          </ListItem>
 | 
				
			||||||
          <Divider variant="inset" component="li" />
 | 
					          <Divider variant="inset" component="li" />
 | 
				
			||||||
          <ListItem>
 | 
					          <ListItem>
 | 
				
			||||||
@@ -76,14 +175,24 @@ class NTPStatusForm extends Component<NTPStatusFormProps> {
 | 
				
			|||||||
          </ListItem>
 | 
					          </ListItem>
 | 
				
			||||||
          <Divider variant="inset" component="li" />
 | 
					          <Divider variant="inset" component="li" />
 | 
				
			||||||
        </List>
 | 
					        </List>
 | 
				
			||||||
        <FormActions>
 | 
					        <Box display="flex" flexWrap="wrap">
 | 
				
			||||||
          <FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData}>
 | 
					          <Box flexGrow={1} padding={1}>
 | 
				
			||||||
            Refresh
 | 
					            <FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData}>
 | 
				
			||||||
          </FormButton>
 | 
					              Refresh
 | 
				
			||||||
        </FormActions>
 | 
					            </FormButton>
 | 
				
			||||||
 | 
					          </Box>
 | 
				
			||||||
 | 
					          {me.admin && !isNtpActive(data) && (
 | 
				
			||||||
 | 
					            <Box flexWrap="none" padding={1} whiteSpace="nowrap">
 | 
				
			||||||
 | 
					              <Button onClick={this.openSetTime} variant="contained" color="primary" startIcon={<AccessTimeIcon />}>
 | 
				
			||||||
 | 
					                Set Time
 | 
				
			||||||
 | 
					              </Button>
 | 
				
			||||||
 | 
					            </Box>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </Box>
 | 
				
			||||||
 | 
					        {this.renderSetTimeDialog()}
 | 
				
			||||||
      </Fragment>
 | 
					      </Fragment>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default withTheme(NTPStatusForm);
 | 
					export default withAuthenticatedContext(withTheme(NTPStatusForm));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,5 @@
 | 
				
			|||||||
import moment from 'moment';
 | 
					import moment, { Moment } from 'moment';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const formatIsoDateTime = (isoDateString: string) => moment.parseZone(isoDateString).format('ll @ HH:mm:ss');
 | 
					export const formatIsoDateTime = (isoDateString: string) => moment.parseZone(isoDateString).format('ll @ HH:mm:ss');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const formatLocalDateTime = (moment: Moment) => moment.format('YYYY-MM-DDTHH:mm');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,3 +17,7 @@ export interface NTPSettings {
 | 
				
			|||||||
  tz_label: string;
 | 
					  tz_label: string;
 | 
				
			||||||
  tz_format: string;
 | 
					  tz_format: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface Time {
 | 
				
			||||||
 | 
					  time_utc: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -70,12 +70,12 @@ class UserForm extends React.Component<UserFormProps> {
 | 
				
			|||||||
            />
 | 
					            />
 | 
				
			||||||
          </DialogContent>
 | 
					          </DialogContent>
 | 
				
			||||||
          <DialogActions>
 | 
					          <DialogActions>
 | 
				
			||||||
            <FormButton variant="contained" color="primary" type="submit" onClick={this.submit}>
 | 
					 | 
				
			||||||
              Done
 | 
					 | 
				
			||||||
            </FormButton>
 | 
					 | 
				
			||||||
            <FormButton variant="contained" color="secondary" onClick={onCancelEditing}>
 | 
					            <FormButton variant="contained" color="secondary" onClick={onCancelEditing}>
 | 
				
			||||||
              Cancel
 | 
					              Cancel
 | 
				
			||||||
            </FormButton>
 | 
					            </FormButton>
 | 
				
			||||||
 | 
					            <FormButton variant="contained" color="primary" type="submit" onClick={this.submit}>
 | 
				
			||||||
 | 
					              Done
 | 
				
			||||||
 | 
					            </FormButton>
 | 
				
			||||||
          </DialogActions>
 | 
					          </DialogActions>
 | 
				
			||||||
        </Dialog>
 | 
					        </Dialog>
 | 
				
			||||||
      </ValidatorForm>
 | 
					      </ValidatorForm>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -118,12 +118,12 @@ class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusForm
 | 
				
			|||||||
          Are you sure you want to restart the device?
 | 
					          Are you sure you want to restart the device?
 | 
				
			||||||
        </DialogContent>
 | 
					        </DialogContent>
 | 
				
			||||||
        <DialogActions>
 | 
					        <DialogActions>
 | 
				
			||||||
          <Button startIcon={<PowerSettingsNewIcon />} variant="contained" onClick={this.onRestartConfirmed} disabled={this.state.processing} color="primary" autoFocus>
 | 
					 | 
				
			||||||
            Restart
 | 
					 | 
				
			||||||
          </Button>
 | 
					 | 
				
			||||||
          <Button variant="contained" onClick={this.onRestartRejected} color="secondary">
 | 
					          <Button variant="contained" onClick={this.onRestartRejected} color="secondary">
 | 
				
			||||||
            Cancel
 | 
					            Cancel
 | 
				
			||||||
          </Button>
 | 
					          </Button>
 | 
				
			||||||
 | 
					          <Button startIcon={<PowerSettingsNewIcon />} variant="contained" onClick={this.onRestartConfirmed} disabled={this.state.processing} color="primary" autoFocus>
 | 
				
			||||||
 | 
					            Restart
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
        </DialogActions>
 | 
					        </DialogActions>
 | 
				
			||||||
      </Dialog>
 | 
					      </Dialog>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
@@ -165,12 +165,12 @@ class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusForm
 | 
				
			|||||||
          Are you sure you want to reset the device to its factory defaults?
 | 
					          Are you sure you want to reset the device to its factory defaults?
 | 
				
			||||||
        </DialogContent>
 | 
					        </DialogContent>
 | 
				
			||||||
        <DialogActions>
 | 
					        <DialogActions>
 | 
				
			||||||
          <ErrorButton startIcon={<SettingsBackupRestoreIcon />} variant="contained" onClick={this.onFactoryResetConfirmed} disabled={this.state.processing} autoFocus>
 | 
					 | 
				
			||||||
            Factory Reset
 | 
					 | 
				
			||||||
          </ErrorButton>
 | 
					 | 
				
			||||||
          <Button variant="contained" onClick={this.onFactoryResetRejected} color="secondary">
 | 
					          <Button variant="contained" onClick={this.onFactoryResetRejected} color="secondary">
 | 
				
			||||||
            Cancel
 | 
					            Cancel
 | 
				
			||||||
          </Button>
 | 
					          </Button>
 | 
				
			||||||
 | 
					          <ErrorButton startIcon={<SettingsBackupRestoreIcon />} variant="contained" onClick={this.onFactoryResetConfirmed} disabled={this.state.processing} autoFocus>
 | 
				
			||||||
 | 
					            Factory Reset
 | 
				
			||||||
 | 
					          </ErrorButton>
 | 
				
			||||||
        </DialogActions>
 | 
					        </DialogActions>
 | 
				
			||||||
      </Dialog>
 | 
					      </Dialog>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,12 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
NTPSettingsService::NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
 | 
					NTPSettingsService::NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
 | 
				
			||||||
    _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager),
 | 
					    _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager),
 | 
				
			||||||
    _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE) {
 | 
					    _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE),
 | 
				
			||||||
 | 
					    _timeHandler(TIME_PATH,
 | 
				
			||||||
 | 
					                 std::bind(&NTPSettingsService::configureTime, this, std::placeholders::_1, std::placeholders::_2)) {
 | 
				
			||||||
 | 
					  _timeHandler.setMethod(HTTP_POST);
 | 
				
			||||||
 | 
					  _timeHandler.setMaxContentLength(MAX_TIME_SIZE);
 | 
				
			||||||
 | 
					  server->addHandler(&_timeHandler);
 | 
				
			||||||
#ifdef ESP32
 | 
					#ifdef ESP32
 | 
				
			||||||
  WiFi.onEvent(
 | 
					  WiFi.onEvent(
 | 
				
			||||||
      std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2),
 | 
					      std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2),
 | 
				
			||||||
@@ -54,6 +59,30 @@ void NTPSettingsService::configureNTP() {
 | 
				
			|||||||
    configTime(_state.tzFormat.c_str(), _state.server.c_str());
 | 
					    configTime(_state.tzFormat.c_str(), _state.server.c_str());
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
 | 
					#ifdef ESP32
 | 
				
			||||||
 | 
					    setenv("TZ", _state.tzFormat.c_str(), 1);
 | 
				
			||||||
 | 
					    tzset();
 | 
				
			||||||
 | 
					#elif defined(ESP8266)
 | 
				
			||||||
 | 
					    setTZ(_state.tzFormat.c_str());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
    sntp_stop();
 | 
					    sntp_stop();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NTPSettingsService::configureTime(AsyncWebServerRequest* request, JsonVariant& json) {
 | 
				
			||||||
 | 
					  if (!sntp_enabled() && json.is<JsonObject>()) {
 | 
				
			||||||
 | 
					    String timeUtc = json["time_utc"];
 | 
				
			||||||
 | 
					    struct tm tm = {0};
 | 
				
			||||||
 | 
					    char* s = strptime(timeUtc.c_str(), "%Y-%m-%dT%H:%M:%SZ", &tm);
 | 
				
			||||||
 | 
					    if (s != nullptr) {
 | 
				
			||||||
 | 
					      time_t time = mktime(&tm);
 | 
				
			||||||
 | 
					      struct timeval now = {.tv_sec = time};
 | 
				
			||||||
 | 
					      settimeofday(&now, nullptr);
 | 
				
			||||||
 | 
					      AsyncWebServerResponse* response = request->beginResponse(200);
 | 
				
			||||||
 | 
					      request->send(response);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  AsyncWebServerResponse* response = request->beginResponse(400);
 | 
				
			||||||
 | 
					  request->send(response);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,9 @@
 | 
				
			|||||||
#define NTP_SETTINGS_FILE "/config/ntpSettings.json"
 | 
					#define NTP_SETTINGS_FILE "/config/ntpSettings.json"
 | 
				
			||||||
#define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings"
 | 
					#define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define MAX_TIME_SIZE 256
 | 
				
			||||||
 | 
					#define TIME_PATH "/rest/time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NTPSettings {
 | 
					class NTPSettings {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  bool enabled;
 | 
					  bool enabled;
 | 
				
			||||||
@@ -62,6 +65,7 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
 | 
				
			|||||||
 private:
 | 
					 private:
 | 
				
			||||||
  HttpEndpoint<NTPSettings> _httpEndpoint;
 | 
					  HttpEndpoint<NTPSettings> _httpEndpoint;
 | 
				
			||||||
  FSPersistence<NTPSettings> _fsPersistence;
 | 
					  FSPersistence<NTPSettings> _fsPersistence;
 | 
				
			||||||
 | 
					  AsyncCallbackJsonWebHandler _timeHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef ESP32
 | 
					#ifdef ESP32
 | 
				
			||||||
  void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
 | 
					  void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
 | 
				
			||||||
@@ -74,6 +78,7 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
 | 
				
			|||||||
  void onStationModeDisconnected(const WiFiEventStationModeDisconnected& event);
 | 
					  void onStationModeDisconnected(const WiFiEventStationModeDisconnected& event);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  void configureNTP();
 | 
					  void configureNTP();
 | 
				
			||||||
 | 
					  void configureTime(AsyncWebServerRequest* request, JsonVariant& json);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif  // end NTPSettingsService_h
 | 
					#endif  // end NTPSettingsService_h
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user