8000 Allow time & date to be configured manually when NTP is inactive (#153) · thisiscam/esp8266-react@55511e0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 55511e0

Browse files
authored
Allow time & date to be configured manually when NTP is inactive (rjwats#153)
* Allow time to be configured manually when NTP is not active * Standarize on primary button on the outside of dialog boxes
1 parent 003fd8a commit 55511e0

File tree

8 files changed

+187
-37
lines changed

8 files changed

+187
-37
lines changed

interface/src/api/Endpoints.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ENDPOINT_ROOT } from './Env';
33
export const FEATURES_ENDPOINT = ENDPOINT_ROOT + "features";
44
export const NTP_STATUS_ENDPOINT = ENDPOINT_ROOT + "ntpStatus";
55
export const NTP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "ntpSettings";
6+
export const TIME_ENDPOINT = ENDPOINT_ROOT + "time";
67
export const AP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "apSettings";
78
export const AP_STATUS_ENDPOINT = ENDPOINT_ROOT + "apStatus";
89
export const SCAN_NETWORKS_ENDPOINT = ENDPOINT_ROOT + "scanNetworks";

interface/src/ntp/NTPStatusForm.tsx

Lines changed: 135 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import React, { Component, Fragment } from 'react';
22
import moment from 'moment';
33

44
import { WithTheme, withTheme } from '@material-ui/core/styles';
5-
import { Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core';
5+
import { Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText, Button } from '@material-ui/core';
6+
import { Dialog, DialogTitle, DialogContent, DialogActions, Box, TextField } from '@material-ui/core';
67

78
import SwapVerticalCircleIcon from '@material-ui/icons/SwapVerticalCircle';
89
import AccessTimeIcon from '@material-ui/icons/AccessTime';
@@ -11,18 +12,116 @@ import UpdateIcon from '@material-ui/icons/Update';
1112
import AvTimerIcon from '@material-ui/icons/AvTimer';
1213
import RefreshIcon from '@material-ui/icons/Refresh';
1314

14-
import { Res 1E11 tFormProps, FormActions, FormButton, HighlightAvatar } from '../components';
15-
15+
import { RestFormProps, FormButton, HighlightAvatar } from '../components';
1616
import { isNtpActive, ntpStatusHighlight, ntpStatus } from './NTPStatus';
17-
import { formatIsoDateTime } from './TimeFormat';
18-
import { NTPStatus } from './types';
17+
import { formatIsoDateTime, formatLocalDateTime } from './TimeFormat';
18+
import { NTPStatus, Time } from './types';
19+
import { redirectingAuthorizedFetch, withAuthenticatedContext, AuthenticatedContextProps } from '../authentication';
20+
import { TIME_ENDPOINT } from '../api';
21+
22+
type NTPStatusFormProps = RestFormProps<NTPStatus> & WithTheme & AuthenticatedContextProps;
1923

20-
type NTPStatusFormProps = RestFormProps<NTPStatus> & WithTheme;
24+
interface NTPStatusFormState {
25+
settingTime: boolean;
26+
localTime: string;
27+
processing: boolean;
28+
}
2129

22-
class NTPStatusForm extends Component<NTPStatusFormProps> {
30+
class NTPStatusForm extends Component<NTPStatusFormProps, NTPStatusFormState> {
31+
32+
constructor(props: NTPStatusFormProps) {
33+
super(props);
34+
this.state = {
35+
settingTime: false,
36+
localTime: '',
37+
processing: false
38+
};
39+
}
40+
41+
updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) => {
42+
this.setState({ localTime: event.target.value });
43+
}
44+
45+
openSetTime = () => {
46+
this.setState({ localTime: formatLocalDateTime(moment()), settingTime: true, });
47+
}
48+
49+
closeSetTime = () => {
50+
this.setState({ settingTime: false });
51+
}
52+
53+
createAdjustedTime = (): Time => {
54+
const currentLocalTime = moment(this.props.data.time_local);
55+
const newLocalTime = moment(this.state.localTime);
56+
newLocalTime.subtract(currentLocalTime.utcOffset())
57+
newLocalTime.milliseconds(0);
58+
newLocalTime.utc();
59+
return {
60+
time_utc: newLocalTime.format()
61+
}
62+
}
63+
64+
configureTime = () => {
65+
this.setState({ processing: true });
66+
redirectingAuthorizedFetch(TIME_ENDPOINT,
67+
{
68+
method: 'POST',
69+
body: JSON.stringify(this.createAdjustedTime()),
70+
headers: {
71+
'Content-Type': 'application/json'
72+
}
73+
})
74+
.then(response => {
75+
if (response.status === 200) {
76+
this.props.enqueueSnackbar("Time set successfully", { variant: 'success' });
77+
this.setState({ processing: false, settingTime: false }, this.props.loadData);
78+
} else {
79+
throw Error("Error setting time, status code: " + response.status);
80+
}
81+
})
82+
.catch(error => {
83+
this.props.enqueueSnackbar(error.message || "Problem setting the time", { variant: 'error' });
84+
this.setState({ processing: false, settingTime: false });
85+
});
86+
}
87+
88+
renderSetTimeDialog() {
89+
return (
90+
<Dialog
91+
open={this.state.settingTime}
92+
onClose={this.closeSetTime}
93+
>
94+
<DialogTitle>Set Time</DialogTitle>
95+
<DialogContent dividers>
96+
<Box mb={2}>Enter local date and time below to set the device's time.</Box>
97+
<TextField
98+
label="Local Time"
99+
type="datetime-local"
100+
value={this.state.localTime}
101+
onChange={this.updateLocalTime}
102+
disabled={this.state.processing}
103+
variant="outlined"
104+
fullWidth
105+
InputLabelProps={{
106+
shrink: true,
107+
}}
108+
/>
109+
</DialogContent>
110+
<DialogActions>
111+
<Button variant="contained" onClick={this.closeSetTime} color="secondary">
112+
Cancel
113+
</Button>
114+
<Button startIcon={<AccessTimeIcon />} variant="contained" onClick={this.configureTime} disabled={this.state.processing} color="primary" autoFocus>
115+
Set Time
116+
</Button>
117+
</DialogActions>
118+
</Dialog>
119+
)
120+
}
23121

24122
render() {
25123
const { data, theme } = this.props
124+
const me = this.props.authenticatedContext.me;
26125
return (
27126
<Fragment>
28127
<List>
@@ -40,30 +139,30 @@ class NTPStatusForm extends Component<NTPStatusFormProps> {
40139
<ListItem>
41140
<ListItemAvatar>
42141
<Avatar>
43-
<AccessTimeIcon />
44-
</Avatar>
45-
</ListItemAvatar>
46-
<ListItemText primary="Local Time" secondary={formatIsoDateTime(data.time_local)} />
47-
</ListItem>
48-
<Divider variant="inset" component="li" />
49-
<ListItem>
50-
<ListItemAvatar>
51-
<Avatar>
52-
<SwapVerticalCircleIcon />
142+
<DNSIcon />
53143
</Avatar>
54144
</ListItemAvatar>
55-
<ListItemText primary="UTC Time" secondary={formatIsoDateTime(data.time_utc)} />
145+
<ListItemText primary="NTP Server" secondary={data.server} />
56146
</ListItem>
57147
<Divider variant="inset" component="li" />
58148
</Fragment>
59149
)}
60150
<ListItem>
61151
<ListItemAvatar>
62152
<Avatar>
63-
<DNSIcon />
153+
<AccessTimeIcon />
64154
</Avatar>
65155
</ListItemAvatar>
66-
<ListItemText primary="NTP Server" secondary={data.server} />
156+
<ListItemText primary="Local Time" secondary={formatIsoDateTime(data.time_local)} />
157+
</ListItem>
158+
<Divider variant="inset" component="li" />
159+
<ListItem>
160+
<ListItemAvatar>
161+
<Avatar>
162+
<SwapVerticalCircleIcon />
163+
</Avatar>
164+
</ListItemAvatar>
165+
<ListItemText primary="UTC Time" secondary={formatIsoDateTime(data.time_utc)} />
67166
</ListItem>
68167
<Divider variant="inset" component="li" />
69168
<ListItem>
@@ -76,14 +175,24 @@ class NTPStatusForm extends Component<NTPStatusFormProps> {
76175
</ListItem>
77176
<Divider variant="inset" component="li" />
78177
</List>
79-
<FormActions>
80-
<FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData}>
81-
Refresh
82-
</FormButton>
83-
</FormActions>
178+
<Box display="flex" flexWrap="wrap">
179+
<Box flexGrow={1} padding={1}>
180+
<FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData}>
181+
Refresh
182+
</FormButton>
183+
</Box>
184+
{me.admin && !isNtpActive(data) && (
185+
<Box flexWrap="none" padding={1} whiteSpace="nowrap">
186+
<Button onClick={this.openSetTime} variant="contained" color="primary" startIcon={<AccessTimeIcon />}>
187+
Set Time
188+
</Button>
189+
</Box>
190+
)}
191+
</Box>
192+
{this.renderSetTimeDialog()}
84193
</Fragment>
85194
);
86195
}
87196
}
88197

89-
export default withTheme(NTPStatusForm);
198+
export default withAuthenticatedContext(withTheme(NTPStatusForm));

interface/src/ntp/TimeFormat.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
import moment from 'moment';
1+
import moment, { Moment } from 'moment';
22

33
export const formatIsoDateTime = (isoDateString: string) => moment.parseZone(isoDateString).format('ll @ HH:mm:ss');
4+
5+
export const formatLocalDateTime = (moment: Moment) => moment.format('YYYY-MM-DDTHH:mm');

interface/src/ntp/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ export interface NTPSettings {
1717
tz_label: string;
1818
tz_format: string;
1919
}
20+
21+
export interface Time {
22+
time_utc: string;
23+
}

interface/src/security/UserForm.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@ class UserForm extends React.Component<UserFormProps> {
7070
/>
7171
</DialogContent>
7272
<DialogActions>
73-
<FormButton variant="contained" color="primary" type="submit" onClick={this.submit}>
74-
Done
75-
</FormButton>
7673
<FormButton variant="contained" color="secondary" onClick={onCancelEditing}>
7774
Cancel
7875
</FormButton>
76+
<FormButton variant="contained" color="primary" type="submit" onClick={this.submit}>
77+
Done
78+
</FormButton>
7979
</DialogActions>
8080
</Dialog>
8181
</ValidatorForm>

interface/src/system/SystemStatusForm.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,12 @@ class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusForm
118118
Are you sure you want to restart the device?
119119
</DialogContent>
120120
<DialogActions>
121-
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" onClick={this.onRestartConfirmed} disabled={this.state.processing} color="primary" autoFocus>
122-
Restart
123-
</Button>
124121
<Button variant="contained" onClick={this.onRestartRejected} color="secondary">
125122
Cancel
126123
</Button>
124+
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" onClick={this.onRestartConfirmed} disabled={this.state.processing} color="primary" autoFocus>
125+
Restart
126+
</Button>
127127
</DialogActions>
128128
</Dialog>
129129
)
@@ -165,12 +165,12 @@ class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusForm
165165
Are you sure you want to reset the device to its factory defaults?
166166
</DialogContent>
167167
<DialogActions>
168-
<ErrorButton startIcon={<SettingsBackupRestoreIcon />} variant="contained" onClick={this.onFactoryResetConfirmed} disabled={this.state.processing} autoFocus>
169-
Factory Reset
170-
</ErrorButton>
171168
<Button variant="contained" onClick={this.onFactoryResetRejected} color="secondary">
172169
Cancel
173170
</Button>
171+
<ErrorButton startIcon={<SettingsBackupRestoreIcon />} variant="contained" onClick={this.onFactoryResetConfirmed} disabled={this.state.processing} autoFocus>
172+
Factory Reset
173+
</ErrorButton>
174174
</DialogActions>
175175
</Dialog>
176176
)

lib/framework/NTPSettingsService.cpp

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
NTPSettingsService::NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
44
_httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager),
5-
_fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE) {
5+
_fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE),
6+
_timeHandler(TIME_PATH,
7+
std::bind(&NTPSettingsService::configureTime, this, std::placeholders::_1, std::placeholders::_2)) {
8+
_timeHandler.setMethod(HTTP_POST);
9+
_timeHandler.setMaxContentLength(MAX_TIME_SIZE);
10+
server->addHandler(&_timeHandler);
611
#ifdef ESP32
712
WiFi.onEvent(
813
std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2),
@@ -54,6 +59,30 @@ void NTPSettingsService::configureNTP() {
5459
configTime(_state.tzFormat.c_str(), _state.server.c_str());
5560
#endif
5661
} else {
62+
#ifdef ESP32
63+
setenv("TZ", _state.tzFormat.c_str(), 1);
64+
tzset();
65+
#elif defined(ESP8266)
66+
setTZ(_state.tzFormat.c_str());
67+
#endif
5768
sntp_stop();
5869
}
5970
}
71+
72+
void NTPSettingsService::configureTime(AsyncWebServerRequest* request, JsonVariant& json) {
73+
if (!sntp_enabled() && json.is<JsonObject>()) {
74+
String timeUtc = json["time_utc"];
75+
struct tm tm = {0};
76+
char* s = strptime(timeUtc.c_str(), "%Y-%m-%dT%H:%M:%SZ", &tm);
77+
if (s != nullptr) {
78+
time_t time = mktime(&tm);
79+
struct timeval now = {.tv_sec = time};
80+
settimeofday(&now, nullptr);
81+
AsyncWebServerResponse* response = request->beginResponse(200);
82+
request->send(response);
83+
return;
84+
}
85+
}
86+
AsyncWebServerResponse* response = request->beginResponse(400);
87+
request->send(response);
88+
}

lib/framework/NTPSettingsService.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
#define NTP_SETTINGS_FILE "/config/ntpSettings.json"
3131
#define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings"
3232

33+
#define MAX_TIME_SIZE 256
34+
#define TIME_PATH "/rest/time"
35+
3336
class NTPSettings {
3437
public:
3538
bool enabled;
@@ -62,6 +65,7 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
6265
private:
6366
HttpEndpoint<NTPSettings> _httpEndpoint;
6467
FSPersistence<NTPSettings> _fsPersistence;
68+
AsyncCallbackJsonWebHandler _timeHandler;
6569

6670
#ifdef ESP32
6771
void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
@@ -74,6 +78,7 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
7478
void onStationModeDisconnected(const WiFiEventStationModeDisconnected& event);
7579
#endif
7680
void configureNTP();
81+
void configureTime(AsyncWebServerRequest* request, JsonVariant& json);
7782
};
7883

7984
#endif // end NTPSettingsService_h

0 commit comments

Comments
 (0)
0