fix upload, except cancel

This commit is contained in:
Proddy
2023-07-01 17:01:08 +02:00
parent fb41606e43
commit 985da48947
12 changed files with 1168 additions and 1186 deletions

View File

@@ -27,13 +27,12 @@
"@mui/material": "^5.13.6", "@mui/material": "^5.13.6",
"@table-library/react-table-library": "4.1.4", "@table-library/react-table-library": "4.1.4",
"@types/lodash-es": "^4.17.7", "@types/lodash-es": "^4.17.7",
"@types/node": "^20.3.2", "@types/node": "^20.3.3",
"@types/react": "^18.2.14", "@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6", "@types/react-dom": "^18.2.6",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"alova": "^2.8.0", "alova": "^2.8.1",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"dev": "^0.1.3",
"history": "^5.3.0", "history": "^5.3.0",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
@@ -42,7 +41,7 @@
"react-dom": "latest", "react-dom": "latest",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-icons": "^4.10.1", "react-icons": "^4.10.1",
"react-router-dom": "^6.14.0", "react-router-dom": "^6.14.1",
"react-toastify": "^9.1.3", "react-toastify": "^9.1.3",
"sockette": "^2.0.6", "sockette": "^2.0.6",
"typesafe-i18n": "^5.24.3", "typesafe-i18n": "^5.24.3",
@@ -53,7 +52,7 @@
"@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1", "@typescript-eslint/parser": "^5.60.1",
"@vitejs/plugin-react-swc": "^3.3.2", "@vitejs/plugin-react-swc": "^3.3.2",
"eslint": "^8.43.0", "eslint": "^8.44.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",

View File

@@ -12,6 +12,7 @@ export const EVENT_SOURCE_ROOT = 'http://' + host + '/es/';
export const alovaInstance = createAlova({ export const alovaInstance = createAlova({
statesHook: ReactHook, statesHook: ReactHook,
// timeout: 3000, // timeout not used because of uploading firmware // timeout: 3000, // timeout not used because of uploading firmware
// localCache: null,
localCache: { localCache: {
GET: { GET: {
mode: 'placeholder', // see https://alova.js.org/learning/response-cache/#cache-replaceholder-mode mode: 'placeholder', // see https://alova.js.org/learning/response-cache/#cache-replaceholder-mode

View File

@@ -26,13 +26,12 @@ const getBorderColor = (theme: Theme, props: DropzoneState) => {
export interface SingleUploadProps { export interface SingleUploadProps {
onDrop: (acceptedFiles: File[]) => void; onDrop: (acceptedFiles: File[]) => void;
onCancel: () => void; onCancel: () => void;
isUploading: boolean; progress: Progress;
progress?: Progress;
// TODO remove
// progress?: AxiosProgressEvent;
} }
const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, isUploading, progress }) => { const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, progress }) => {
const isUploading = progress.total > 0;
const dropzoneState = useDropzone({ const dropzoneState = useDropzone({
onDrop, onDrop,
accept: { accept: {
@@ -43,20 +42,16 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, isUploading, pr
disabled: isUploading, disabled: isUploading,
multiple: false multiple: false
}); });
const { getRootProps, getInputProps } = dropzoneState; const { getRootProps, getInputProps } = dropzoneState;
const theme = useTheme(); const theme = useTheme();
const { LL } = useI18nContext(); const { LL } = useI18nContext();
// TODO remove debug
console.log('progress', progress?.loaded);
const progressText = () => { const progressText = () => {
if (isUploading) { if (isUploading) {
if (progress?.total) { if (progress.total) {
return LL.UPLOADING() + ': ' + Math.round((progress.loaded * 100) / progress.total) + '%'; return LL.UPLOADING() + ': ' + Math.round((progress.loaded * 100) / progress.total) + '%';
} }
return LL.UPLOADING() + `\u2026`;
} }
return LL.UPLOAD_DROP_TEXT(); return LL.UPLOAD_DROP_TEXT();
}; };
@@ -86,8 +81,8 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, isUploading, pr
<Fragment> <Fragment>
<Box width="100%" p={2}> <Box width="100%" p={2}>
<LinearProgress <LinearProgress
variant={!progress || progress.total ? 'determinate' : 'indeterminate'} variant="determinate"
value={!progress ? 0 : progress.total ? Math.round((progress.loaded * 100) / progress.total) : 0} value={progress.total === 0 ? 0 : Math.round((progress.loaded * 100) / progress.total)}
/> />
</Box> </Box>
<Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}> <Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}>

View File

@@ -1,2 +1 @@
export { default as SingleUpload } from './SingleUpload'; export { default as SingleUpload } from './SingleUpload';
export { default as useFileUpload } from './useFileUpload';

View File

@@ -1,94 +0,0 @@
import { useRequest } from 'alova';
// import axios from 'axios';
import { useCallback, useEffect, useState } from 'react';
import { toast } from 'react-toastify';
// import type { FileUploadConfig } from 'api/endpoints';
// import type { AxiosPromise, CancelTokenSource, AxiosProgressEvent } from 'axios';
import * as SystemApi from 'api/system';
import { useI18nContext } from 'i18n/i18n-react';
interface MediaUploadOptions {
// TODO fileupload move to alova
// upload: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
upload: (file: File) => Promise<void>;
}
const useFileUpload = ({ upload }: MediaUploadOptions) => {
const { LL } = useI18nContext();
// const [uploading, setUploading] = useState<boolean>(false);
const [md5, setMd5] = useState<string>('');
// const [uploadProgress, setUploadProgress] = useState<AxiosProgressEvent>();
// const [uploadCancelToken, setUploadCancelToken] = useState<CancelTokenSource>();
const { uploading, send: sendUpload } = useRequest(SystemApi.uploadFile, {
immediate: false
});
const resetUploadingStates = () => {
// setUploading(false);
// setUploadProgress(undefined);
// setUploadCancelToken(undefined);
setMd5('');
};
// const cancelUpload = useCallback(() => {
// uploadCancelToken?.cancel();
// resetUploadingStates();
// }, [uploadCancelToken]);
// useEffect(
// () => () => {
// uploadCancelToken?.cancel();
// },
// [uploadCancelToken]
// );
// TODO fileupload move to alova
// TODO make it single file
const uploadFile = async (files: File[]) => {
// TODO remove debug
console.log('useFileUpload.ts:uploadFile:' + files[0].name, files[0].size);
await sendUpload(files[0]);
// const response = await SystemApi.startUploadFile(files[0]);
// console.log(response.status);
// const response = await upload(files[0], {
// onUploadProgress: setUploadProgress,
// cancelToken: cancelToken.token
// });
// try {
// const cancelToken = axios.CancelToken.source();
// setUploadCancelToken(cancelToken);
// setUploading(true);
// const response = await upload(images[0], {
// onUploadProgress: setUploadProgress,
// cancelToken: cancelToken.token
// });
// resetUploadingStates();
// if (response.status === 200) {
// toast.success(LL.UPLOAD() + ' ' + LL.SUCCESSFUL());
// } else if (response.status === 201) {
// setMd5(String(response.data));
// toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
// }
// } catch (error) {
// if (axios.isCancel(error)) {
// toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
// } else {
// resetUploadingStates();
// toast.error(LL.UPLOAD() + ' ' + LL.FAILED(0));
// }
// }
};
return [uploadFile, uploading, md5] as const;
// return [uploadFile, cancelUpload, uploading, uploadProgress, md5] as const;
};
export default useFileUpload;

View File

@@ -1,177 +0,0 @@
import DownloadIcon from '@mui/icons-material/GetApp';
import { Typography, Button, Box } from '@mui/material';
import { useRequest } from 'alova';
import { toast } from 'react-toastify';
import type { FC } from 'react';
import * as SystemApi from 'api/system';
import { SingleUpload, useFileUpload } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from 'project/api';
interface UploadFileProps {
// TODO fileupload move to alova
// uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
uploadGeneralFile: (file: File) => Promise<void>;
}
const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
const { LL } = useI18nContext();
// TODO remove these
const md5 = '';
const cancelUpload = () => {};
const uploading = false;
// const [uploadFile, cancelUpload, uploading, uploadProgress, md5] = useFileUpload({ upload: uploadGeneralFile });
// const [uploadFile, cancelUpload, uploading, md5] = useFileUpload({ upload: uploadGeneralFile });
const { send: getSettings, onSuccess: onSuccessGetSettings } = useRequest(EMSESP.getSettings(), {
immediate: false
});
const { send: getCustomizations, onSuccess: onSuccessgetCustomizations } = useRequest(EMSESP.getCustomizations(), {
immediate: false
});
const { send: getEntities, onSuccess: onSuccessGetEntities } = useRequest(EMSESP.getEntities(), {
immediate: false
});
const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest(EMSESP.getSchedule(), {
immediate: false
});
const {
loading: isUploading,
uploading: progress,
send: sendUpload
} = useRequest(SystemApi.uploadFile, {
immediate: false,
force: true
});
const uploadFile = async (files: File[]) => {
// TODO remove debug
console.log('useFileUpload.ts:uploadFile:' + files[0].name, files[0].size);
await sendUpload(files[0]);
};
// TODO see if refactor like https://alova.js.org/extension/alova-adapter-xhr/#download
// And use revoke
const saveFile = (json: any, endpoint: string) => {
const a = document.createElement('a');
const filename = 'emsesp_' + endpoint + '.json';
a.href = URL.createObjectURL(
new Blob([JSON.stringify(json, null, 2)], {
type: 'text/plain'
})
);
a.setAttribute('download', filename);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
toast.info(LL.DOWNLOAD_SUCCESSFUL());
};
onSuccessGetSettings((event) => {
saveFile(event.data, 'settings');
});
onSuccessgetCustomizations((event) => {
saveFile(event.data, 'customizations');
});
onSuccessGetEntities((event) => {
saveFile(event.data, 'entities');
});
onSuccessGetSchedule((event) => {
saveFile(event.data, 'schedule');
});
const downloadSettings = async () => {
await getSettings().catch((error) => {
toast.error(error.message);
});
};
const downloadCustomizations = async () => {
await getCustomizations().catch((error) => {
toast.error(error.message);
});
};
const downloadEntities = async () => {
await getEntities().catch((error) => {
toast.error(error.message);
});
};
const downloadSchedule = async () => {
await getSchedule().catch((error) => {
toast.error(error.message);
});
};
return (
<>
{!uploading && (
<>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.UPLOAD()}
</Typography>
<Box mb={2} color="warning.main">
<Typography variant="body2">{LL.UPLOAD_TEXT()} </Typography>
</Box>
</>
)}
{md5 !== '' && (
<Box mb={2}>
<Typography variant="body2">{'MD5: ' + md5}</Typography>
</Box>
)}
{/* TODO fix this hardcoded isUploading */}
<SingleUpload onDrop={uploadFile} onCancel={cancelUpload} isUploading={isUploading} progress={progress} />
{console.log(progress)}
{/* <SingleUpload onDrop={uploadFile} onCancel={cancelUpload} isUploading={false} progress={uploading} /> */}
{!isUploading && (
<>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.DOWNLOAD(0)}
</Typography>
<Box color="warning.main">
<Typography mb={1} variant="body2">
{LL.DOWNLOAD_SETTINGS_TEXT()}
</Typography>
</Box>
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadSettings}>
{LL.SETTINGS_OF('')}
</Button>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '}
</Typography>
</Box>
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadCustomizations}>
{LL.CUSTOMIZATIONS()}
</Button>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={downloadEntities}
>
{LL.CUSTOM_ENTITIES(0)}
</Button>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_SCHEDULE_TEXT()}{' '}
</Typography>
</Box>
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadSchedule}>
{LL.SCHEDULE(0)}
</Button>
</>
)}
</>
);
};
export default GeneralFileUpload;

View File

@@ -1,43 +1,174 @@
import DownloadIcon from '@mui/icons-material/GetApp';
import { Typography, Button, Box } from '@mui/material';
import { useRequest } from 'alova'; import { useRequest } from 'alova';
import { useRef, useState } from 'react'; import { useState, type FC } from 'react';
import GeneralFileUpload from './GeneralFileUpload'; import { toast } from 'react-toastify';
import RestartMonitor from './RestartMonitor'; import RestartMonitor from './RestartMonitor';
import type { FC } from 'react';
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { SectionContent } from 'components'; import { SectionContent, SingleUpload } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from 'project/api';
const UploadFileForm: FC = () => { const UploadFileForm: FC = () => {
const [restarting, setRestarting] = useState<boolean>();
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [restarting, setRestarting] = useState<boolean>(false);
const [md5, setMd5] = useState<string>();
const { const { send: getSettings, onSuccess: onSuccessGetSettings } = useRequest(EMSESP.getSettings(), {
loading, immediate: false
data, });
uploading, const { send: getCustomizations, onSuccess: onSuccessgetCustomizations } = useRequest(EMSESP.getCustomizations(), {
send: sendUpload immediate: false
} = useRequest(SystemApi.uploadFile, { });
const { send: getEntities, onSuccess: onSuccessGetEntities } = useRequest(EMSESP.getEntities(), {
immediate: false
});
const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest(EMSESP.getSchedule(), {
immediate: false immediate: false
}); });
const uploadFile = useRef(async (file: File) => { const {
// TODO fileupload move to alova loading: isUploading,
console.log('UploadFileForm.tsx: uploadFile duplicate!!!'); // TODO do we need this function??? duplicate? uploading: progress,
await sendUpload(file); send: sendUpload,
onSuccess: onSuccessUpload,
// const response = await SystemApi.uploadFile(file); abort
// if (response.status === 200) { } = useRequest(SystemApi.uploadFile, {
// setRestarting(true); immediate: false,
// } force: true
// return response;
}); });
onSuccessUpload(({ data }: any) => {
if (data) {
setMd5(data.md5);
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
} else {
toast.success(LL.UPLOAD() + ' ' + LL.SUCCESSFUL());
setRestarting(true);
}
});
const cancelUpload = () => {
console.log('aborting upload...'); // TODO remove debug
abort();
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
};
const startUpload = async (files: File[]) => {
await sendUpload(files[0]);
};
const saveFile = (json: any, endpoint: string) => {
const anchor = document.createElement('a');
anchor.href = URL.createObjectURL(
new Blob([JSON.stringify(json, null, 2)], {
type: 'text/plain'
})
);
anchor.download = 'emsesp_' + endpoint + '.json';
anchor.click();
URL.revokeObjectURL(anchor.href);
toast.info(LL.DOWNLOAD_SUCCESSFUL());
};
onSuccessGetSettings((event) => {
saveFile(event.data, 'settings');
});
onSuccessgetCustomizations((event) => {
saveFile(event.data, 'customizations');
});
onSuccessGetEntities((event) => {
saveFile(event.data, 'entities');
});
onSuccessGetSchedule((event) => {
saveFile(event.data, 'schedule');
});
const downloadSettings = async () => {
await getSettings().catch((error) => {
toast.error(error.message);
});
};
const downloadCustomizations = async () => {
await getCustomizations().catch((error) => {
toast.error(error.message);
});
};
const downloadEntities = async () => {
await getEntities().catch((error) => {
toast.error(error.message);
});
};
const downloadSchedule = async () => {
await getSchedule().catch((error) => {
toast.error(error.message);
});
};
const content = () => (
<>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.UPLOAD()}
</Typography>
<Box mb={2} color="warning.main">
<Typography variant="body2">{LL.UPLOAD_TEXT()} </Typography>
</Box>
{md5 && (
<Box mb={2}>
<Typography variant="body2">{'MD5: ' + md5}</Typography>
</Box>
)}
<SingleUpload onDrop={startUpload} onCancel={cancelUpload} progress={progress} />
{!isUploading && (
<>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.DOWNLOAD(0)}
</Typography>
<Box color="warning.main">
<Typography mb={1} variant="body2">
{LL.DOWNLOAD_SETTINGS_TEXT()}
</Typography>
</Box>
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadSettings}>
{LL.SETTINGS_OF('')}
</Button>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '}
</Typography>
</Box>
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadCustomizations}>
{LL.CUSTOMIZATIONS()}
</Button>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={downloadEntities}
>
{LL.CUSTOM_ENTITIES(0)}
</Button>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_SCHEDULE_TEXT()}{' '}
</Typography>
</Box>
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadSchedule}>
{LL.SCHEDULE(0)}
</Button>
</>
)}
</>
);
return ( return (
<SectionContent title={LL.UPLOAD_DOWNLOAD()} titleGutter> <SectionContent title={LL.UPLOAD_DOWNLOAD()} titleGutter>
{restarting ? <RestartMonitor /> : <GeneralFileUpload uploadGeneralFile={uploadFile.current} />} {restarting ? <RestartMonitor /> : content()}
</SectionContent> </SectionContent>
); );
}; };

View File

@@ -45,7 +45,7 @@ export const scanDevices = () => alovaInstance.Post('/rest/scanDevices');
// HelpInformation // HelpInformation
export const API = (apiCall: APIcall) => alovaInstance.Post('/api', apiCall); export const API = (apiCall: APIcall) => alovaInstance.Post('/api', apiCall);
// GeneralFileUpload // UploadFileForm
export const getSettings = () => alovaInstance.Get('/rest/getSettings'); export const getSettings = () => alovaInstance.Get('/rest/getSettings');
export const getCustomizations = () => alovaInstance.Get('/rest/getCustomizations'); export const getCustomizations = () => alovaInstance.Get('/rest/getCustomizations');
export const getEntities = () => alovaInstance.Get<Entities>('/rest/getEntities'); export const getEntities = () => alovaInstance.Get<Entities>('/rest/getEntities');

File diff suppressed because it is too large Load Diff

View File

@@ -42,7 +42,7 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
return; return;
} else { } else {
md5[0] = '\0'; md5[0] = '\0';
return; // not support file type return; // unsupported file type
} }
if (is_firmware) { if (is_firmware) {
@@ -115,7 +115,7 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
} }
// check if it was a firmware upgrade // check if it was a firmware upgrade
// if no error, send the success response // if no error, send the success response as a JSON
if (is_firmware && !request->_tempObject) { if (is_firmware && !request->_tempObject) {
request->onDisconnect(RestartService::restartNow); request->onDisconnect(RestartService::restartNow);
AsyncWebServerResponse * response = request->beginResponse(200); AsyncWebServerResponse * response = request->beginResponse(200);
@@ -123,8 +123,13 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
return; return;
} }
if (strlen(md5) == 32) { if (strlen(md5) == 32) {
AsyncWebServerResponse * response = request->beginResponse(201, "text/plain", md5); // created auto * response = new AsyncJsonResponse(false, 256);
JsonObject root = response->getRoot();
root["md5"] = md5;
response->setLength();
request->send(response); request->send(response);
// AsyncWebServerResponse * response = request->beginResponse(201, "text/plain", md5); // created
// request->send(response);
return; return;
} }

View File

@@ -24,7 +24,7 @@ function progress_middleware(req, res, next) {
const percentage = (progress / file_size) * 100; const percentage = (progress / file_size) * 100;
console.log(`Progress: ${Math.round(percentage)}%`); console.log(`Progress: ${Math.round(percentage)}%`);
// await delay(1000); // slow it down // await delay(1000); // slow it down
delay_blocking(200); // slow it down delay_blocking(100); // slow it down
}); });
next(); // invoke next middleware which is multer next(); // invoke next middleware which is multer
} }
@@ -2151,18 +2151,21 @@ rest_server.post(FACTORY_RESET_ENDPOINT, (req, res) => {
rest_server.post(UPLOAD_FILE_ENDPOINT, progress_middleware, upload.single('file'), (req, res) => { rest_server.post(UPLOAD_FILE_ENDPOINT, progress_middleware, upload.single('file'), (req, res) => {
console.log('command: uploadFile completed.'); console.log('command: uploadFile completed.');
console.log(req.file);
if (req.file) { if (req.file) {
const filename = req.file.originalname;
const ext = filename.substring(filename.lastIndexOf('.') + 1);
console.log(req.file);
console.log('ext: ' + ext);
if (ext === 'bin') {
return res.sendStatus(200); return res.sendStatus(200);
} else if (ext === 'md5') {
return res.json({ md5: 'ef4304fc4d9025a58dcf25d71c882d2c' });
}
} }
return res.sendStatus(400); return res.sendStatus(400);
}); });
rest_server.post(SIGN_IN_ENDPOINT, (req, res) => {
// res.sendStatus(401); // test bad user
console.log('Signed in as ' + req.body.username);
res.json(signin); // watch out, this has a return value
});
rest_server.get(GENERATE_TOKEN_ENDPOINT, (req, res) => { rest_server.get(GENERATE_TOKEN_ENDPOINT, (req, res) => {
res.json(generate_token); res.json(generate_token);
}); });

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.6.0-dev.13a" #define EMSESP_APP_VERSION "3.6.0-dev.13b"