mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 08:19:52 +03:00
Feature: upload customization settings from a file #256
This commit is contained in:
@@ -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="/"
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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' });
|
||||
|
||||
@@ -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;
|
||||
28
interface/src/framework/system/GeneralFileUpload.tsx
Normal file
28
interface/src/framework/system/GeneralFileUpload.tsx
Normal 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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -159,7 +159,7 @@ const SystemStatusForm: FC = () => {
|
||||
<Typography variant="body2">
|
||||
Use
|
||||
<Link target="_blank" href={uploadURL} color="primary">
|
||||
{'UPLOAD FIRMWARE'}
|
||||
{'UPLOAD'}
|
||||
</Link>
|
||||
to apply the new firmware
|
||||
</Typography>
|
||||
|
||||
26
interface/src/framework/system/UploadFileForm.tsx
Normal file
26
interface/src/framework/system/UploadFileForm.tsx
Normal 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;
|
||||
@@ -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;
|
||||
@@ -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'
|
||||
@@ -112,26 +112,30 @@ const HelpInformation: FC = () => {
|
||||
|
||||
{me.admin && (
|
||||
<>
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="primary">
|
||||
Export Data
|
||||
</Typography>
|
||||
<Box color="warning.main">
|
||||
<Typography variant="body2">
|
||||
Download the current system information, application settings and any customizations using the buttons
|
||||
below.
|
||||
<Typography sx={{ pb: 1 }} variant="body2">
|
||||
Download the current system information to show EMS statistics and connected devices
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="secondary" onClick={() => onDownload('info')}>
|
||||
system info
|
||||
</Button>
|
||||
<Box color="warning.main">
|
||||
<Typography sx={{ pt: 2 }} variant="body2">
|
||||
Export the application settings and any customizations to a JSON file. These files can later be uploaded
|
||||
via the System menu.
|
||||
</Typography>
|
||||
<Typography sx={{ pt: 2 }} variant="body2">
|
||||
Be careful when sharing the settings as the file contains passwords and other sensitive system
|
||||
information.
|
||||
</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"
|
||||
@@ -157,7 +161,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
|
||||
<StarIcon style={{ color: '#fdff3a' }} /> on
|
||||
<StarIcon style={{ fontSize: 16, color: '#fdff3a', verticalAlign: 'middle' }} /> on
|
||||
<Link href="https://github.com/emsesp/EMS-ESP32" color="primary">
|
||||
{'GitHub'}
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user