This commit is contained in:
proddy
2021-05-14 12:45:57 +02:00
parent 15df0c0552
commit fec5ff3132
108 changed files with 3508 additions and 2455 deletions

View File

@@ -1,6 +1,11 @@
import React, { Component } from 'react';
import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components';
import {
restController,
RestControllerProps,
RestFormLoader,
SectionContent
} from '../components';
import { OTA_SETTINGS_ENDPOINT } from '../api';
import OTASettingsForm from './OTASettingsForm';
@@ -9,7 +14,6 @@ import { OTASettings } from './types';
type OTASettingsControllerProps = RestControllerProps<OTASettings>;
class OTASettingsController extends Component<OTASettingsControllerProps> {
componentDidMount() {
this.props.loadData();
}
@@ -19,12 +23,11 @@ class OTASettingsController extends Component<OTASettingsControllerProps> {
<SectionContent title="OTA Settings" titleGutter>
<RestFormLoader
{...this.props}
render={formProps => <OTASettingsForm {...formProps} />}
render={(formProps) => <OTASettingsForm {...formProps} />}
/>
</SectionContent>
);
}
}
export default restController(OTA_SETTINGS_ENDPOINT, OTASettingsController);

View File

@@ -4,7 +4,13 @@ import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
import { Checkbox } from '@material-ui/core';
import SaveIcon from '@material-ui/icons/Save';
import { RestFormProps, BlockFormControlLabel, PasswordValidator, FormButton, FormActions } from '../components';
import {
RestFormProps,
BlockFormControlLabel,
PasswordValidator,
FormButton,
FormActions
} from '../components';
import { isIP, isHostname, or } from '../validators';
import { OTASettings } from './types';
@@ -12,7 +18,6 @@ import { OTASettings } from './types';
type OTASettingsFormProps = RestFormProps<OTASettings>;
class OTASettingsForm extends React.Component<OTASettingsFormProps> {
componentDidMount() {
ValidatorForm.addValidationRule('isIPOrHostname', or(isIP, isHostname));
}
@@ -25,14 +30,24 @@ class OTASettingsForm extends React.Component<OTASettingsFormProps> {
control={
<Checkbox
checked={data.enabled}
onChange={handleValueChange("enabled")}
onChange={handleValueChange('enabled')}
/>
}
label="Enable OTA Updates"
/>
<TextValidator
validators={['required', 'isNumber', 'minNumber:1025', 'maxNumber:65535']}
errorMessages={['Port is required', "Must be a number", "Must be greater than 1024 ", "Max value is 65535"]}
validators={[
'required',
'isNumber',
'minNumber:1025',
'maxNumber:65535'
]}
errorMessages={[
'Port is required',
'Must be a number',
'Must be greater than 1024 ',
'Max value is 65535'
]}
name="port"
label="Port"
fullWidth
@@ -44,7 +59,10 @@ class OTASettingsForm extends React.Component<OTASettingsFormProps> {
/>
<PasswordValidator
validators={['required', 'matchRegexp:^.{1,64}$']}
errorMessages={['OTA Password is required', 'OTA Point Password must be 64 characters or less']}
errorMessages={[
'OTA Password is required',
'OTA Point Password must be 64 characters or less'
]}
name="password"
label="Password"
fullWidth
@@ -54,7 +72,12 @@ class OTASettingsForm extends React.Component<OTASettingsFormProps> {
margin="normal"
/>
<FormActions>
<FormButton startIcon={<SaveIcon />} variant="contained" color="primary" type="submit">
<FormButton
startIcon={<SaveIcon />}
variant="contained"
color="primary"
type="submit"
>
Save
</FormButton>
</FormActions>

View File

@@ -1,22 +1,27 @@
import React, { Component } from 'react';
import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'
import { Component } from 'react';
import { Redirect, Switch, RouteComponentProps } from 'react-router-dom';
import { Tabs, Tab } from '@material-ui/core';
import { WithFeaturesProps, withFeatures } from '../features/FeaturesContext';
import { withAuthenticatedContext, AuthenticatedContextProps, AuthenticatedRoute } from '../authentication';
import {
withAuthenticatedContext,
AuthenticatedContextProps,
AuthenticatedRoute
} from '../authentication';
import { MenuAppBar } from '../components';
import SystemStatusController from './SystemStatusController';
import OTASettingsController from './OTASettingsController';
import UploadFirmwareController from './UploadFirmwareController';
type SystemProps = AuthenticatedContextProps & RouteComponentProps & WithFeaturesProps;
type SystemProps = AuthenticatedContextProps &
RouteComponentProps &
WithFeaturesProps;
class System extends Component<SystemProps> {
handleTabChange = (event: React.ChangeEvent<{}>, path: string) => {
handleTabChange = (path: string) => {
this.props.history.push(path);
};
@@ -24,27 +29,51 @@ class System extends Component<SystemProps> {
const { authenticatedContext, features } = this.props;
return (
<MenuAppBar sectionTitle="System">
<Tabs value={this.props.match.url} onChange={this.handleTabChange} variant="fullWidth">
<Tabs
value={this.props.match.url}
onChange={(e, path) => this.handleTabChange(path)}
variant="fullWidth"
>
<Tab value="/system/status" label="System Status" />
{features.ota && (
<Tab value="/system/ota" label="OTA Settings" disabled={!authenticatedContext.me.admin} />
<Tab
value="/system/ota"
label="OTA Settings"
disabled={!authenticatedContext.me.admin}
/>
)}
{features.upload_firmware && (
<Tab value="/system/upload" label="Upload Firmware" disabled={!authenticatedContext.me.admin} />
<Tab
value="/system/upload"
label="Upload Firmware"
disabled={!authenticatedContext.me.admin}
/>
)}
</Tabs>
<Switch>
<AuthenticatedRoute exact path="/system/status" component={SystemStatusController} />
<AuthenticatedRoute
exact
path="/system/status"
component={SystemStatusController}
/>
{features.ota && (
<AuthenticatedRoute exact path="/system/ota" component={OTASettingsController} />
<AuthenticatedRoute
exact
path="/system/ota"
component={OTASettingsController}
/>
)}
{features.upload_firmware && (
<AuthenticatedRoute exact path="/system/upload" component={UploadFirmwareController} />
<AuthenticatedRoute
exact
path="/system/upload"
component={UploadFirmwareController}
/>
)}
<Redirect to="/system/status" />
</Switch>
</MenuAppBar>
)
);
}
}

View File

@@ -1,6 +1,11 @@
import React, { Component } from 'react';
import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components';
import {
restController,
RestControllerProps,
RestFormLoader,
SectionContent
} from '../components';
import { SYSTEM_STATUS_ENDPOINT } from '../api';
import SystemStatusForm from './SystemStatusForm';
@@ -9,7 +14,6 @@ import { SystemStatus } from './types';
type SystemStatusControllerProps = RestControllerProps<SystemStatus>;
class SystemStatusController extends Component<SystemStatusControllerProps> {
componentDidMount() {
this.props.loadData();
}
@@ -19,12 +23,11 @@ class SystemStatusController extends Component<SystemStatusControllerProps> {
<SectionContent title="System Status">
<RestFormLoader
{...this.props}
render={formProps => <SystemStatusForm {...formProps} />}
render={(formProps) => <SystemStatusForm {...formProps} />}
/>
</SectionContent>
);
}
}
export default restController(SYSTEM_STATUS_ENDPOINT, SystemStatusController);

View File

@@ -1,4 +1,4 @@
import React, { Component } from 'react';
import { Component } from 'react';
import { SectionContent } from '../components';
import { UPLOAD_FIRMWARE_ENDPOINT } from '../api';
@@ -12,8 +12,10 @@ interface UploadFirmwareControllerState {
progress?: ProgressEvent;
}
class UploadFirmwareController extends Component<WithSnackbarProps, UploadFirmwareControllerState> {
class UploadFirmwareController extends Component<
WithSnackbarProps,
UploadFirmwareControllerState
> {
state: UploadFirmwareControllerState = {
xhr: undefined,
progress: undefined
@@ -25,47 +27,67 @@ class UploadFirmwareController extends Component<WithSnackbarProps, UploadFirmwa
updateProgress = (progress: ProgressEvent) => {
this.setState({ progress });
}
};
uploadFile = (file: File) => {
if (this.state.xhr) {
return;
}
var xhr = new XMLHttpRequest();
const xhr = new XMLHttpRequest();
this.setState({ xhr });
redirectingAuthorizedUpload(xhr, UPLOAD_FIRMWARE_ENDPOINT, file, this.updateProgress).then(() => {
if (xhr.status !== 200) {
throw Error("Invalid status code: " + xhr.status);
}
this.props.enqueueSnackbar("Activating new firmware", { variant: 'success' });
this.setState({ xhr: undefined, progress: undefined });
}).catch((error: Error) => {
if (error.name === 'AbortError') {
this.props.enqueueSnackbar("Upload cancelled by user", { variant: 'warning' });
} else {
const errorMessage = error.name === 'UploadError' ? "Error during upload" : (error.message || "Unknown error");
this.props.enqueueSnackbar("Problem uploading: " + errorMessage, { variant: 'error' });
redirectingAuthorizedUpload(
xhr,
UPLOAD_FIRMWARE_ENDPOINT,
file,
this.updateProgress
)
.then(() => {
if (xhr.status !== 200) {
throw Error('Invalid status code: ' + xhr.status);
}
this.props.enqueueSnackbar('Activating new firmware', {
variant: 'success'
});
this.setState({ xhr: undefined, progress: undefined });
}
});
}
})
.catch((error: Error) => {
if (error.name === 'AbortError') {
this.props.enqueueSnackbar('Upload cancelled by user', {
variant: 'warning'
});
} else {
const errorMessage =
error.name === 'UploadError'
? 'Error during upload'
: error.message || 'Unknown error';
this.props.enqueueSnackbar('Problem uploading: ' + errorMessage, {
variant: 'error'
});
this.setState({ xhr: undefined, progress: undefined });
}
});
};
cancelUpload = () => {
if (this.state.xhr) {
this.state.xhr.abort();
this.setState({ xhr: undefined, progress: undefined });
}
}
};
render() {
const { xhr, progress } = this.state;
return (
<SectionContent title="Upload Firmware">
<UploadFirmwareForm onFileSelected={this.uploadFile} onCancel={this.cancelUpload} uploading={!!xhr} progress={progress} />
<UploadFirmwareForm
onFileSelected={this.uploadFile}
onCancel={this.cancelUpload}
uploading={!!xhr}
progress={progress}
/>
</SectionContent>
);
}
}
export default withSnackbar(UploadFirmwareController);

View File

@@ -1,6 +1,6 @@
import React, { Fragment } from "react";
import { SingleUpload } from "../components";
import { Box } from "@material-ui/core";
import React, { Fragment } from 'react';
import { SingleUpload } from '../components';
import { Box } from '@material-ui/core';
interface UploadFirmwareFormProps {
uploading: boolean;
@@ -22,8 +22,10 @@ class UploadFirmwareForm extends React.Component<UploadFirmwareFormProps> {
return (
<Fragment>
<Box py={2}>
Upload a new firmware file (.bin or .bin.gz) below to replace the existing firmware.
<p></p>This can take up to a minute. Wait until you see "Activating new Firmware" and EMS-ESP will then automatically restart.
Upload a new firmware file (.bin or .bin.gz) below to replace the
existing firmware.
<p></p>This can take up to a minute. Wait until you see "Activating
new Firmware" and EMS-ESP will then automatically restart.
</Box>
<SingleUpload
onDrop={this.handleDrop}

View File

@@ -1,37 +1,38 @@
export enum EspPlatform {
ESP8266 = 'esp8266',
ESP32 = 'esp32',
ESP32 = 'esp32'
}
interface ESPSystemStatus {
esp_platform: EspPlatform
max_alloc_heap: number
cpu_freq_mhz: number
free_heap: number
sdk_version: string
flash_chip_size: number
flash_chip_speed: number
fs_used: number
fs_total: number
uptime: string
free_mem: number
emsesp_version: string;
esp_platform: EspPlatform;
max_alloc_heap: number;
cpu_freq_mhz: number;
free_heap: number;
sdk_version: string;
flash_chip_size: number;
flash_chip_speed: number;
fs_used: number;
fs_total: number;
uptime: string;
free_mem: number;
}
export interface ESP32SystemStatus extends ESPSystemStatus {
esp_platform: EspPlatform.ESP32
psram_size: number
free_psram: number
esp_platform: EspPlatform.ESP32;
psram_size: number;
free_psram: number;
}
export interface ESP8266SystemStatus extends ESPSystemStatus {
esp_platform: EspPlatform.ESP8266
heap_fragmentation: number
esp_platform: EspPlatform.ESP8266;
heap_fragmentation: number;
}
export type SystemStatus = ESP8266SystemStatus | ESP32SystemStatus
export type SystemStatus = ESP8266SystemStatus | ESP32SystemStatus;
export interface OTASettings {
enabled: boolean
port: number
password: string
enabled: boolean;
port: number;
password: string;
}