Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into idf4_no_master

This commit is contained in:
MichaelDvP
2022-05-08 18:21:48 +02:00
40 changed files with 523 additions and 435 deletions

View File

@@ -47,10 +47,7 @@ const AppRouting: FC = () => {
<RemoveTrailingSlashes />
<Routes>
<Route path="/unauthorized" element={<RootRedirect message="Please sign in to continue" signOut />} />
<Route
path="/firmwareUpdated"
element={<RootRedirect message="Firmware update successful" variant="success" />}
/>
<Route path="/fileUpdated" element={<RootRedirect message="Upload successful" variant="success" />} />
{features.security && (
<Route
path="/"

View File

@@ -92,7 +92,7 @@ export interface FileUploadConfig {
onUploadProgress?: (progressEvent: ProgressEvent) => void;
}
export const uploadFile = (url: string, file: File, config?: FileUploadConfig): AxiosPromise<void> => {
export const startUploadFile = (url: string, file: File, config?: FileUploadConfig): AxiosPromise<void> => {
const formData = new FormData();
formData.append('file', file);

View File

@@ -2,7 +2,7 @@ import { AxiosPromise } from 'axios';
import { OTASettings, SystemStatus, LogSettings, LogEntries } from '../types';
import { AXIOS, AXIOS_BIN, FileUploadConfig, uploadFile } from './endpoints';
import { AXIOS, AXIOS_BIN, FileUploadConfig, startUploadFile } from './endpoints';
export function readSystemStatus(timeout?: number): AxiosPromise<SystemStatus> {
return AXIOS.get('/systemStatus', { timeout });
@@ -24,8 +24,8 @@ export function updateOTASettings(otaSettings: OTASettings): AxiosPromise<OTASet
return AXIOS.post('/otaSettings', otaSettings);
}
export const uploadFirmware = (file: File, config?: FileUploadConfig): AxiosPromise<void> =>
uploadFile('/uploadFirmware', file, config);
export const uploadFile = (file: File, config?: FileUploadConfig): AxiosPromise<void> =>
startUploadFile('/uploadFile', file, config);
export function readLogSettings(): AxiosPromise<LogSettings> {
return AXIOS.get('/logSettings');

View File

@@ -32,7 +32,8 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
const dropzoneState = useDropzone({
onDrop,
accept: {
'application/octet-stream': ['.bin']
'application/octet-stream': ['.bin'],
'application/json': ['.json']
},
disabled: uploading,
multiple: false

View File

@@ -42,7 +42,7 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => {
cancelToken: cancelToken.token
});
resetUploadingStates();
enqueueSnackbar('Upload successful', { variant: 'success' });
enqueueSnackbar('File uploaded', { variant: 'success' });
} catch (error: unknown) {
if (axios.isCancel(error)) {
enqueueSnackbar('Upload aborted', { variant: 'warning' });

View File

@@ -83,14 +83,13 @@ const ManageUsersForm: FC = () => {
const noAdminConfigured = () => !data.users.find((u) => u.admin);
const removeUser = (toRemove: User) => {
const users = data.users.filter((u) => u.id !== toRemove.id);
const users = data.users.filter((u) => u.username !== toRemove.username);
setData({ ...data, users });
};
const createUser = () => {
setCreating(true);
setUser({
id: '',
username: '',
password: '',
admin: true
@@ -108,7 +107,7 @@ const ManageUsersForm: FC = () => {
const doneEditingUser = () => {
if (user) {
const users = [...data.users.filter((u) => u.id !== user.id), user];
const users = [...data.users.filter((u) => u.username !== user.username), user];
setData({ ...data, users });
setUser(undefined);
}
@@ -118,8 +117,8 @@ const ManageUsersForm: FC = () => {
setGeneratingToken(undefined);
};
const generateToken = (id: string) => {
setGeneratingToken(id);
const generateToken = (username: string) => {
setGeneratingToken(username);
};
const onSubmit = async () => {
@@ -127,9 +126,11 @@ const ManageUsersForm: FC = () => {
authenticatedContext.refresh();
};
const user_table = data.users.map((u) => ({ ...u, id: u.username }));
return (
<>
<Table data={{ nodes: data.users }} theme={table_theme}>
<Table data={{ nodes: user_table }} theme={table_theme}>
{(tableList: any) => (
<>
<Header>
@@ -140,16 +141,16 @@ const ManageUsersForm: FC = () => {
</HeaderRow>
</Header>
<Body>
{tableList.map((u: User, index: number) => (
{tableList.map((u: any) => (
<Row key={u.id} item={u}>
<Cell>{u.id}</Cell>
<Cell>{u.username}</Cell>
<Cell>{u.admin ? <CheckIcon /> : <CloseIcon />}</Cell>
<Cell>
<IconButton
size="small"
disabled={!authenticatedContext.me.admin}
aria-label="Generate Token"
onClick={() => generateToken(u.id)}
onClick={() => generateToken(u.username)}
>
<VpnKeyIcon />
</IconButton>

View File

@@ -1,33 +0,0 @@
import { AxiosPromise } from 'axios';
import { FC } from 'react';
import { FileUploadConfig } from '../../api/endpoints';
import { MessageBox, SingleUpload, useFileUpload } from '../../components';
interface UploadFirmwareProps {
uploadFirmware: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
}
const FirmwareFileUpload: FC<UploadFirmwareProps> = ({ uploadFirmware }) => {
const [uploadFile, cancelUpload, uploading, uploadProgress] = useFileUpload({ upload: uploadFirmware });
return (
<>
{!uploading && (
<MessageBox
message="Upload a new firmware (.bin) file below to replace the existing firmware"
level="warning"
my={2}
/>
)}
<SingleUpload
onDrop={uploadFile}
onCancel={cancelUpload}
uploading={uploading}
progress={uploadProgress}
/>
</>
);
};
export default FirmwareFileUpload;

View File

@@ -0,0 +1,28 @@
import { AxiosPromise } from 'axios';
import { FC } from 'react';
import { FileUploadConfig } from '../../api/endpoints';
import { MessageBox, SingleUpload, useFileUpload } from '../../components';
interface UploadFileProps {
uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
}
const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
const [uploadFile, cancelUpload, uploading, uploadProgress] = useFileUpload({ upload: uploadGeneralFile });
return (
<>
{!uploading && (
<MessageBox
message="Upload a new firmware (.bin) file or an exported settings or customizations (.json) file below. EMS-ESP will restart afterwards to apply the new changes."
level="warning"
my={2}
/>
)}
<SingleUpload onDrop={uploadFile} onCancel={cancelUpload} uploading={uploading} progress={uploadProgress} />
</>
);
};
export default GeneralFileUpload;

View File

@@ -8,7 +8,7 @@ const RESTART_TIMEOUT = 2 * 60 * 1000;
const POLL_TIMEOUT = 2000;
const POLL_INTERVAL = 5000;
const FirmwareRestartMonitor: FC = () => {
const RestartMonitor: FC = () => {
const [failed, setFailed] = useState<boolean>(false);
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
@@ -16,7 +16,7 @@ const FirmwareRestartMonitor: FC = () => {
const poll = useRef(async () => {
try {
await SystemApi.readSystemStatus(POLL_TIMEOUT);
document.location.href = '/firmwareUpdated';
document.location.href = '/fileUpdated';
} catch (error: unknown) {
if (new Date().getTime() < timeoutAt.current) {
setTimeoutId(setTimeout(poll.current, POLL_INTERVAL));
@@ -40,4 +40,4 @@ const FirmwareRestartMonitor: FC = () => {
);
};
export default FirmwareRestartMonitor;
export default RestartMonitor;

View File

@@ -6,7 +6,7 @@ import { Tab } from '@mui/material';
import { useRouterTab, RouterTabs, useLayoutTitle, RequireAdmin } from '../../components';
import { AuthenticatedContext } from '../../contexts/authentication';
import { FeaturesContext } from '../../contexts/features';
import UploadFirmwareForm from './UploadFirmwareForm';
import UploadFileForm from './UploadFileForm';
import SystemStatusForm from './SystemStatusForm';
import OTASettingsForm from './OTASettingsForm';
@@ -26,7 +26,7 @@ const System: FC = () => {
<Tab value="log" label="System Log" />
{features.ota && <Tab value="ota" label="OTA Settings" disabled={!me.admin} />}
{features.upload_firmware && <Tab value="upload" label="Upload Firmware" disabled={!me.admin} />}
{features.upload_firmware && <Tab value="upload" label="Upload" disabled={!me.admin} />}
</RouterTabs>
<Routes>
<Route path="status" element={<SystemStatusForm />} />
@@ -46,7 +46,7 @@ const System: FC = () => {
path="upload"
element={
<RequireAdmin>
<UploadFirmwareForm />
<UploadFileForm />
</RequireAdmin>
}
/>

View File

@@ -159,7 +159,7 @@ const SystemStatusForm: FC = () => {
<Typography variant="body2">
Use&nbsp;
<Link target="_blank" href={uploadURL} color="primary">
{'UPLOAD FIRMWARE'}
{'UPLOAD'}
</Link>
&nbsp;to apply the new firmware
</Typography>

View File

@@ -0,0 +1,26 @@
import { FC, useRef, useState } from 'react';
import * as SystemApi from '../../api/system';
import { SectionContent } from '../../components';
import { FileUploadConfig } from '../../api/endpoints';
import GeneralFileUpload from './GeneralFileUpload';
import RestartMonitor from './RestartMonitor';
const UploadFileForm: FC = () => {
const [restarting, setRestarting] = useState<boolean>();
const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => {
const response = await SystemApi.uploadFile(file, config);
setRestarting(true);
return response;
});
return (
<SectionContent title="Upload File" titleGutter>
{restarting ? <RestartMonitor /> : <GeneralFileUpload uploadGeneralFile={uploadFile.current} />}
</SectionContent>
);
};
export default UploadFileForm;

View File

@@ -1,26 +0,0 @@
import { FC, useRef, useState } from 'react';
import * as SystemApi from '../../api/system';
import { SectionContent } from '../../components';
import { FileUploadConfig } from '../../api/endpoints';
import FirmwareFileUpload from './FirmwareFileUpload';
import FirmwareRestartMonitor from './FirmwareRestartMonitor';
const UploadFirmwareForm: FC = () => {
const [restarting, setRestarting] = useState<boolean>();
const uploadFirmware = useRef(async (file: File, config?: FileUploadConfig) => {
const response = await SystemApi.uploadFirmware(file, config);
setRestarting(true);
return response;
});
return (
<SectionContent title="Upload Firmware" titleGutter>
{restarting ? <FirmwareRestartMonitor /> : <FirmwareFileUpload uploadFirmware={uploadFirmware.current} />}
</SectionContent>
);
};
export default UploadFirmwareForm;

View File

@@ -2,7 +2,7 @@ import { FC, useContext } from 'react';
import { Typography, Button, Box, List, ListItem, ListItemText, Link, ListItemAvatar } from '@mui/material';
import { SectionContent, ButtonRow } from '../components';
import { SectionContent, ButtonRow, MessageBox } from '../components';
import { AuthenticatedContext } from '../contexts/authentication';
@@ -36,7 +36,7 @@ const HelpInformation: FC = () => {
} else {
const json = response.data;
const a = document.createElement('a');
const filename = 'emsesp_' + endpoint + '.txt';
const filename = 'emsesp_' + endpoint + '.json';
a.href = URL.createObjectURL(
new Blob([JSON.stringify(json, null, 2)], {
type: 'text/plain'
@@ -102,9 +102,13 @@ const HelpInformation: FC = () => {
<GitHubIcon />
</ListItemAvatar>
<ListItemText>
To report an issue or request a feature, please do via&nbsp;
To report an issue or request a feature, please&nbsp;
<Link component="button" variant="body1" onClick={() => onDownload('info')}>
download
</Link>
&nbsp;the debug information and include in a new&nbsp;
<Link target="_blank" href="https://github.com/emsesp/EMS-ESP32/issues/new/choose" color="primary">
{'GitHub'}
GitHub issue
</Link>
</ListItemText>
</ListItem>
@@ -112,26 +116,17 @@ const HelpInformation: FC = () => {
{me.admin && (
<>
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
Export Data
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
Download Settings
</Typography>
<Box color="warning.main">
<Typography variant="body2">
Download the current system information, application settings and any customizations using the buttons
below.
Export the application settings and any customizations to a JSON file. These files can later be uploaded
via System&rarr;Upload.
</Typography>
</Box>
<Box sx={{ display: 'flex' }}>
<ButtonRow>
<Button
startIcon={<DownloadIcon />}
variant="outlined"
color="secondary"
onClick={() => onDownload('info')}
>
system info
</Button>
<Button
startIcon={<DownloadIcon />}
variant="outlined"
@@ -150,6 +145,12 @@ const HelpInformation: FC = () => {
</Button>
</ButtonRow>
</Box>
<MessageBox
my={2}
level="warning"
message="Be careful when sharing your Settings as the file contains passwords and other sensitive system
information!"
/>
</>
)}
@@ -157,7 +158,7 @@ const HelpInformation: FC = () => {
<Typography align="center" variant="h6">
EMS-ESP is a free and open-source project.
<br></br>Please consider supporting us by giving it a&nbsp;
<StarIcon style={{ color: '#fdff3a' }} /> on&nbsp;
<StarIcon style={{ fontSize: 16, color: '#fdff3a', verticalAlign: 'middle' }} /> on&nbsp;
<Link href="https://github.com/emsesp/EMS-ESP32" color="primary">
{'GitHub'}
</Link>

View File

@@ -1,5 +1,4 @@
export interface User {
id: string; // needed for Table
username: string;
password: string;
admin: boolean;

View File

@@ -2,9 +2,9 @@ import { AxiosError } from 'axios';
export const extractErrorMessage = (error: unknown, defaultMessage: string) => {
if (error instanceof AxiosError) {
return error.response && error.response.data && error?.response?.data?.message;
return defaultMessage + ' (' + error.request.statusText + ')';
} else if (error instanceof Error) {
return error.message;
return defaultMessage + ' (' + error.message + ')';
}
return defaultMessage;
};