mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-08 00:39:50 +03:00
more alova ports
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
InputAdornment,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
// eslint-disable-next-line import/named
|
||||
import { updateState, useRequest } from 'alova';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
@@ -29,6 +30,7 @@ import type { FC } from 'react';
|
||||
|
||||
import type { NetworkSettings } from 'types';
|
||||
import * as NetworkApi from 'api/network';
|
||||
import * as SystemApi from 'api/system';
|
||||
import {
|
||||
BlockFormControlLabel,
|
||||
ButtonRow,
|
||||
@@ -40,7 +42,7 @@ import {
|
||||
BlockNavigation
|
||||
} from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import * as EMSESP from 'project/api';
|
||||
|
||||
import { numberValue, updateValueDirty, useRest2 } from 'utils';
|
||||
|
||||
import { validate } from 'validators';
|
||||
@@ -71,7 +73,7 @@ const WiFiSettingsForm: FC = () => {
|
||||
update: NetworkApi.updateNetworkSettings
|
||||
});
|
||||
|
||||
const { send: restartCommand } = useRequest(EMSESP.restart(), {
|
||||
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
|
||||
@@ -1,82 +1,63 @@
|
||||
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
|
||||
import { Button } from '@mui/material';
|
||||
import { useEffect, useState, useCallback, useRef } from 'react';
|
||||
import { useRequest } from 'alova';
|
||||
import { useState, useCallback, useRef } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import WiFiNetworkSelector from './WiFiNetworkSelector';
|
||||
import type { FC } from 'react';
|
||||
import type { WiFiNetwork, WiFiNetworkList } from 'types';
|
||||
import * as NetworkApi from 'api/network';
|
||||
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
||||
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
const NUM_POLLS = 10;
|
||||
const POLLING_FREQUENCY = 500;
|
||||
|
||||
const compareNetworks = (network1: WiFiNetwork, network2: WiFiNetwork) => {
|
||||
if (network1.rssi < network2.rssi) return 1;
|
||||
if (network1.rssi > network2.rssi) return -1;
|
||||
return 0;
|
||||
};
|
||||
const POLLING_FREQUENCY = 1000;
|
||||
|
||||
const WiFiNetworkScanner: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const pollCount = useRef(0);
|
||||
const [networkList, setNetworkList] = useState<WiFiNetworkList>();
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
|
||||
const { data: networkList, send: getNetworkList } = useRequest(NetworkApi.listNetworks, {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const {
|
||||
send: scanNetworks,
|
||||
onSuccess: onSuccessScanNetworks,
|
||||
onError: onErrorScanNetworks
|
||||
} = useRequest(NetworkApi.scanNetworks);
|
||||
|
||||
const finishedWithError = useCallback((message: string) => {
|
||||
toast.error(message);
|
||||
setNetworkList(undefined);
|
||||
setErrorMessage(message);
|
||||
pollCount.current = 0;
|
||||
}, []);
|
||||
|
||||
const pollNetworkList = useCallback(async () => {
|
||||
try {
|
||||
const response = await NetworkApi.listNetworks();
|
||||
if (response.status === 202) {
|
||||
const completedPollCount = pollCount.current + 1;
|
||||
if (completedPollCount < NUM_POLLS) {
|
||||
pollCount.current = completedPollCount;
|
||||
setTimeout(pollNetworkList, POLLING_FREQUENCY);
|
||||
} else {
|
||||
finishedWithError(LL.PROBLEM_LOADING());
|
||||
}
|
||||
} else {
|
||||
const newNetworkList = response.data;
|
||||
newNetworkList.networks.sort(compareNetworks);
|
||||
setNetworkList(newNetworkList);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message);
|
||||
onErrorScanNetworks((event) => {
|
||||
console.log('onErrorScanNetworks'); // TODO fix
|
||||
if (event.error?.message === 'Wait') {
|
||||
// 202
|
||||
console.log('not ready...: ', event.error?.message); // TODO fix
|
||||
const completedPollCount = pollCount.current + 1;
|
||||
if (completedPollCount < NUM_POLLS) {
|
||||
pollCount.current = completedPollCount;
|
||||
setTimeout(scanNetworks, POLLING_FREQUENCY);
|
||||
} else {
|
||||
finishedWithError(LL.PROBLEM_LOADING());
|
||||
}
|
||||
} else {
|
||||
finishedWithError(LL.PROBLEM_LOADING());
|
||||
}
|
||||
}, [finishedWithError, LL]);
|
||||
});
|
||||
|
||||
const startNetworkScan = useCallback(async () => {
|
||||
onSuccessScanNetworks(() => {
|
||||
console.log('onCompleteScanNetworks'); // TODO fix
|
||||
pollCount.current = 0;
|
||||
setNetworkList(undefined);
|
||||
setErrorMessage(undefined);
|
||||
try {
|
||||
await NetworkApi.scanNetworks();
|
||||
setTimeout(pollNetworkList, POLLING_FREQUENCY);
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message);
|
||||
} else {
|
||||
finishedWithError(LL.PROBLEM_LOADING());
|
||||
}
|
||||
}
|
||||
}, [finishedWithError, pollNetworkList, LL]);
|
||||
|
||||
useEffect(() => {
|
||||
void startNetworkScan();
|
||||
}, [startNetworkScan]);
|
||||
void getNetworkList(); // fetch the list
|
||||
});
|
||||
|
||||
const renderNetworkScanner = () => {
|
||||
if (!networkList) {
|
||||
@@ -93,7 +74,7 @@ const WiFiNetworkScanner: FC = () => {
|
||||
startIcon={<PermScanWifiIcon />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={startNetworkScan}
|
||||
onClick={scanNetworks}
|
||||
disabled={!errorMessage && !networkList}
|
||||
>
|
||||
{LL.SCAN_AGAIN()}…
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { Button, Checkbox, MenuItem } from '@mui/material';
|
||||
// eslint-disable-next-line import/named
|
||||
import { updateState } from 'alova';
|
||||
import { useState } from 'react';
|
||||
import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ';
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useI18nContext } from 'i18n/i18n-react';
|
||||
import * as EMSESP from 'project/api';
|
||||
|
||||
interface UploadFileProps {
|
||||
// TODO fileupload upload move to alova
|
||||
uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,21 +18,31 @@ import {
|
||||
} from 'components';
|
||||
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||
import { numberValue, updateValueDirty, useRest2 } from 'utils';
|
||||
|
||||
import { validate } from 'validators';
|
||||
import { OTA_SETTINGS_VALIDATOR } from 'validators/system';
|
||||
|
||||
const OTASettingsForm: FC = () => {
|
||||
const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
|
||||
useRest<OTASettings>({
|
||||
read: SystemApi.readOTASettings,
|
||||
update: SystemApi.updateOTASettings
|
||||
});
|
||||
const {
|
||||
loadData,
|
||||
saveData,
|
||||
saving,
|
||||
updateDataValue,
|
||||
data,
|
||||
origData,
|
||||
dirtyFlags,
|
||||
setDirtyFlags,
|
||||
blocker,
|
||||
errorMessage
|
||||
} = useRest2<OTASettings>({
|
||||
read: SystemApi.readOTASettings,
|
||||
update: SystemApi.updateOTASettings
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
|
||||
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue);
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useRequest } from 'alova';
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
@@ -16,10 +17,14 @@ const RestartMonitor: FC = () => {
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { send: readSystemStatus } = useRequest((timeout) => SystemApi.readSystemStatus(timeout), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT);
|
||||
const poll = useRef(async () => {
|
||||
try {
|
||||
await SystemApi.readSystemStatus(POLL_TIMEOUT);
|
||||
await readSystemStatus(POLL_TIMEOUT);
|
||||
document.location.href = '/fileUpdated';
|
||||
} catch (error) {
|
||||
if (new Date().getTime() < timeoutAt.current) {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { Box, styled, Button, Checkbox, MenuItem, Grid, TextField } from '@mui/material';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
// eslint-disable-next-line import/named
|
||||
import { useRequest } from 'alova';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import type { LogSettings, LogEntry, LogEntries } from 'types';
|
||||
import type { LogSettings, LogEntry } from 'types';
|
||||
import { addAccessTokenParameter } from 'api/authentication';
|
||||
import { EVENT_SOURCE_ROOT } from 'api/endpoints';
|
||||
import * as SystemApi from 'api/system';
|
||||
@@ -14,7 +16,7 @@ import { SectionContent, FormLoader, BlockFormControlLabel, BlockNavigation } fr
|
||||
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { LogLevel } from 'types';
|
||||
import { useRest, updateValueDirty, extractErrorMessage } from 'utils';
|
||||
import { updateValueDirty, useRest2 } from 'utils';
|
||||
|
||||
export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log';
|
||||
|
||||
@@ -49,15 +51,20 @@ const levelLabel = (level: LogLevel) => {
|
||||
const SystemLog: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
// TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus);
|
||||
const { loadData, data, setData, origData, dirtyFlags, blocker, setDirtyFlags, setOrigData } = useRest<LogSettings>({
|
||||
read: SystemApi.readLogSettings
|
||||
});
|
||||
const { loadData, data, updateDataValue, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
|
||||
useRest2<LogSettings>({
|
||||
read: SystemApi.readLogSettings,
|
||||
update: SystemApi.updateLogSettings
|
||||
});
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
const [logEntries, setLogEntries] = useState<LogEntries>({ events: [] });
|
||||
// called on page load to reset pointer and fetch all log entries
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { send: fetchLog } = useRequest(SystemApi.fetchLog());
|
||||
const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
|
||||
const [lastIndex, setLastIndex] = useState<number>(0);
|
||||
|
||||
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue);
|
||||
|
||||
const paddedLevelLabel = (level: LogLevel) => {
|
||||
const label = levelLabel(level);
|
||||
return data?.compact ? ' ' + label[0] : label.padStart(8, '\xa0');
|
||||
@@ -73,11 +80,9 @@ const SystemLog: FC = () => {
|
||||
return data?.compact ? label : label.padEnd(7, '\xa0');
|
||||
};
|
||||
|
||||
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
|
||||
|
||||
const onDownload = () => {
|
||||
let result = '';
|
||||
for (const i of logEntries.events) {
|
||||
for (const i of logEntries) {
|
||||
result += i.t + ' ' + levelLabel(i.l) + ' ' + i.i + ': [' + i.n + '] ' + i.m + '\n';
|
||||
}
|
||||
const a = document.createElement('a');
|
||||
@@ -94,29 +99,22 @@ const SystemLog: FC = () => {
|
||||
const logentry = JSON.parse(rawData as string) as LogEntry;
|
||||
if (logentry.i > lastIndex) {
|
||||
setLastIndex(logentry.i);
|
||||
setLogEntries((old) => ({ events: [...old.events, logentry] }));
|
||||
setLogEntries((log) => [...log, logentry]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetchLog = useCallback(async () => {
|
||||
try {
|
||||
await SystemApi.readLogEntries();
|
||||
} catch (error) {
|
||||
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
}, [LL]);
|
||||
|
||||
useEffect(() => {
|
||||
void fetchLog();
|
||||
}, [fetchLog]);
|
||||
const saveSettings = async () => {
|
||||
await saveData();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const es = new EventSource(addAccessTokenParameter(LOG_EVENTSOURCE_URL));
|
||||
es.onmessage = onMessage;
|
||||
es.onerror = () => {
|
||||
es.close();
|
||||
window.location.reload();
|
||||
toast.error('EventSource failed');
|
||||
// window.location.reload();
|
||||
};
|
||||
|
||||
return () => {
|
||||
@@ -124,28 +122,6 @@ const SystemLog: FC = () => {
|
||||
};
|
||||
});
|
||||
|
||||
const saveSettings = async () => {
|
||||
if (data) {
|
||||
try {
|
||||
const response = await SystemApi.updateLogSettings({
|
||||
level: data.level,
|
||||
max_messages: data.max_messages,
|
||||
compact: data.compact
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
toast.error(LL.PROBLEM_UPDATING());
|
||||
} else {
|
||||
setOrigData(response.data);
|
||||
setDirtyFlags([]);
|
||||
toast.success(LL.UPDATED_OF(LL.SETTINGS()));
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
@@ -231,17 +207,16 @@ const SystemLog: FC = () => {
|
||||
p: 1
|
||||
}}
|
||||
>
|
||||
{logEntries &&
|
||||
logEntries.events.map((e) => (
|
||||
<LogEntryLine key={e.i}>
|
||||
<span>{e.t}</span>
|
||||
{data.compact && <span>{paddedLevelLabel(e.l)} </span>}
|
||||
{!data.compact && <span>{paddedLevelLabel(e.l)} </span>}
|
||||
<span>{paddedIDLabel(e.i)} </span>
|
||||
<span>{paddedNameLabel(e.n)} </span>
|
||||
<span>{e.m}</span>
|
||||
</LogEntryLine>
|
||||
))}
|
||||
{logEntries.map((e) => (
|
||||
<LogEntryLine key={e.i}>
|
||||
<span>{e.t}</span>
|
||||
{data.compact && <span>{paddedLevelLabel(e.l)} </span>}
|
||||
{!data.compact && <span>{paddedLevelLabel(e.l)} </span>}
|
||||
<span>{paddedIDLabel(e.i)} </span>
|
||||
<span>{paddedNameLabel(e.n)} </span>
|
||||
<span>{e.m}</span>
|
||||
</LogEntryLine>
|
||||
))}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -35,12 +35,11 @@ import { toast } from 'react-toastify';
|
||||
import RestartMonitor from './RestartMonitor';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import type { SystemStatus, Version } from 'types';
|
||||
import type { Version } from 'types';
|
||||
import * as SystemApi from 'api/system';
|
||||
import { ButtonRow, FormLoader, SectionContent, MessageBox } from 'components';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { extractErrorMessage } from 'utils';
|
||||
|
||||
export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest';
|
||||
export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest';
|
||||
@@ -52,9 +51,6 @@ function formatNumber(num: number) {
|
||||
|
||||
const SystemStatusForm: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const [restarting, setRestarting] = useState<boolean>();
|
||||
|
||||
const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus);
|
||||
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
const [confirmRestart, setConfirmRestart] = useState<boolean>(false);
|
||||
@@ -63,6 +59,21 @@ const SystemStatusForm: FC = () => {
|
||||
const [showingVersion, setShowingVersion] = useState<boolean>(false);
|
||||
const [latestVersion, setLatestVersion] = useState<Version>();
|
||||
const [latestDevVersion, setLatestDevVersion] = useState<Version>();
|
||||
const [restarting, setRestarting] = useState<boolean>();
|
||||
|
||||
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const { send: factoryResetCommand } = useRequest(SystemApi.factoryReset(), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const { send: partitionCommand } = useRequest(SystemApi.partition(), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus);
|
||||
|
||||
useEffect(() => {
|
||||
void axios.get(VERSIONCHECK_ENDPOINT).then((response) => {
|
||||
@@ -83,30 +94,47 @@ const SystemStatusForm: FC = () => {
|
||||
|
||||
const restart = async () => {
|
||||
setProcessing(true);
|
||||
try {
|
||||
const response = await SystemApi.restart();
|
||||
if (response.status === 200) {
|
||||
await restartCommand()
|
||||
.then(() => {
|
||||
setRestarting(true);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
} finally {
|
||||
setConfirmRestart(false);
|
||||
setProcessing(false);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setConfirmRestart(false);
|
||||
setProcessing(false);
|
||||
});
|
||||
};
|
||||
|
||||
const factoryReset = async () => {
|
||||
setProcessing(true);
|
||||
await factoryResetCommand()
|
||||
.then(() => {
|
||||
setRestarting(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setConfirmFactoryReset(false);
|
||||
setProcessing(false);
|
||||
});
|
||||
};
|
||||
|
||||
const partition = async () => {
|
||||
setProcessing(true);
|
||||
try {
|
||||
await SystemApi.partition();
|
||||
setRestarting(true);
|
||||
} catch (error) {
|
||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
} finally {
|
||||
setConfirmRestart(false);
|
||||
setProcessing(false);
|
||||
}
|
||||
await partitionCommand()
|
||||
.then(() => {
|
||||
setRestarting(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setConfirmRestart(false);
|
||||
setProcessing(false);
|
||||
});
|
||||
};
|
||||
|
||||
const renderRestartDialog = () => (
|
||||
@@ -201,19 +229,6 @@ const SystemStatusForm: FC = () => {
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
const factoryReset = async () => {
|
||||
setProcessing(true);
|
||||
try {
|
||||
await SystemApi.factoryReset();
|
||||
setRestarting(true);
|
||||
} catch (error) {
|
||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
||||
} finally {
|
||||
setConfirmFactoryReset(false);
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderFactoryResetDialog = () => (
|
||||
<Dialog open={confirmFactoryReset} onClose={() => setConfirmFactoryReset(false)}>
|
||||
<DialogTitle>{LL.FACTORY_RESET()}</DialogTitle>
|
||||
@@ -243,7 +258,7 @@ const SystemStatusForm: FC = () => {
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -15,6 +15,7 @@ const UploadFileForm: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => {
|
||||
// TODO fileupload move to alova
|
||||
const response = await SystemApi.uploadFile(file, config);
|
||||
if (response.status === 200) {
|
||||
setRestarting(true);
|
||||
|
||||
Reference in New Issue
Block a user