This commit is contained in:
proddy
2024-08-08 12:39:48 +02:00
parent dc53ff42f6
commit 3481a879c2
59 changed files with 259 additions and 453 deletions

View File

@@ -1,5 +1,4 @@
import { useState } from 'react';
import type { FC } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning';
@@ -29,7 +28,7 @@ export const isAPEnabled = ({ provision_mode }: APSettingsType) =>
provision_mode === APProvisionMode.AP_MODE_ALWAYS ||
provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
const APSettings: FC = () => {
const APSettings = () => {
const {
loadData,
saving,

View File

@@ -1,5 +1,4 @@
import { useState } from 'react';
import type { FC } from 'react';
import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel';
@@ -49,9 +48,9 @@ export function boardProfileSelectItems() {
));
}
const ApplicationSettings: FC = () => {
const ApplicationSettings = () => {
const { data: hardwareData } = useRequest(SystemApi.readHardwareStatus, {
force: true
initialData: { psram: false }
});
const {
@@ -84,19 +83,12 @@ const ApplicationSettings: FC = () => {
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const {
loading: processingBoard,
send: readBoardProfile,
onSuccess: onSuccessBoardProfile
} = useRequest((boardProfile: string) => EMSESP.getBoardProfile(boardProfile), {
immediate: false
});
const { send: restartCommand } = useRequest(SystemApi.restart(), {
immediate: false
});
onSuccessBoardProfile((event) => {
const { loading: processingBoard, send: readBoardProfile } = useRequest(
(boardProfile: string) => EMSESP.getBoardProfile(boardProfile),
{
immediate: false
}
).onSuccess((event) => {
const response = event.data as Settings;
updateDataValue({
...data,
@@ -113,6 +105,10 @@ const ApplicationSettings: FC = () => {
});
});
const { send: restartCommand } = useRequest(SystemApi.restart(), {
immediate: false
});
const updateBoardProfile = async (board_profile: string) => {
await readBoardProfile(board_profile).catch((error: Error) => {
toast.error(error.message);

View File

@@ -1,5 +1,4 @@
import { useState } from 'react';
import type { FC } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning';
@@ -31,7 +30,7 @@ import type { MqttSettingsType } from 'types';
import { numberValue, updateValueDirty, useRest } from 'utils';
import { createMqttSettingsValidator, validate } from 'validators';
const MqttSettings: FC = () => {
const MqttSettings = () => {
const {
loadData,
saving,

View File

@@ -1,11 +1,11 @@
import { useState } from 'react';
import type { FC } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning';
import { Button, Checkbox, MenuItem } from '@mui/material';
import * as NTPApi from 'api/ntp';
import { readNTPSettings } from 'api/ntp';
import { updateState } from 'alova/client';
import type { ValidateFieldsError } from 'async-validator';
@@ -26,7 +26,7 @@ import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
import { TIME_ZONES, selectedTimeZone, timeZoneSelectItems } from './TZ';
const NTPSettings: FC = () => {
const NTPSettings = () => {
const {
loadData,
saving,
@@ -72,9 +72,7 @@ const NTPSettings: FC = () => {
const changeTimeZone = (event: React.ChangeEvent<HTMLInputElement>) => {
updateFormValue(event);
// TODO fix
updateState('ntpSettings', (settings: NTPSettingsType) => ({
void updateState(readNTPSettings(), (settings: NTPSettingsType) => ({
...settings,
tz_label: event.target.value,
tz_format: TIME_ZONES[event.target.value]

View File

@@ -1,4 +1,4 @@
import { type FC, useState } from 'react';
import { useState } from 'react';
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import CancelIcon from '@mui/icons-material/Cancel';
@@ -29,7 +29,7 @@ import { SectionContent, useLayoutTitle } from 'components';
import ListMenuItem from 'components/layout/ListMenuItem';
import { useI18nContext } from 'i18n/i18n-react';
const Settings: FC = () => {
const Settings = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.SETTINGS(0));

View File

@@ -1,4 +1,4 @@
import { type FC, useState } from 'react';
import { useState } from 'react';
import { toast } from 'react-toastify';
import DownloadIcon from '@mui/icons-material/GetApp';
@@ -19,61 +19,59 @@ import { useI18nContext } from 'i18n/i18n-react';
import RestartMonitor from '../status/RestartMonitor';
const UploadDownload: FC = () => {
const UploadDownload = () => {
const { LL } = useI18nContext();
const [restarting, setRestarting] = useState<boolean>();
const [md5, setMd5] = useState<string>();
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 { send: getAPI, onSuccess: onGetAPI } = useRequest(
(data: APIcall) => EMSESP.API(data),
{
immediate: false
}
);
const { send: getSettings } = useRequest(EMSESP.getSettings(), {
immediate: false
}).onSuccess((event) => {
saveFile(event.data, 'settings.json');
});
const { send: getCustomizations } = useRequest(EMSESP.getCustomizations(), {
immediate: false
}).onSuccess((event) => {
saveFile(event.data, 'customizations.json');
});
const { send: getEntities } = useRequest(EMSESP.getEntities(), {
immediate: false
}).onSuccess((event) => {
saveFile(event.data, 'entities.json');
});
const { send: getSchedule } = useRequest(EMSESP.getSchedule(), {
immediate: false
}).onSuccess((event) => {
saveFile(event.data, 'schedule.json');
});
const { send: getAPI } = useRequest((data: APIcall) => EMSESP.API(data), {
immediate: false
}).onSuccess((event) => {
saveFile(
event.data,
String(event.args[0].device) + '_' + String(event.args[0].entity) + '.txt'
);
});
const {
data: data,
send: loadData,
error
} = useRequest(SystemApi.readHardwareStatus, { force: true });
} = useRequest(SystemApi.readHardwareStatus);
const { data: latestVersion } = useRequest(SystemApi.getStableVersion, {
immediate: true,
force: true
});
const { data: latestDevVersion } = useRequest(SystemApi.getDevVersion, {
immediate: true,
force: true
});
// called immediately to get the latest version, on page load
const { data: latestVersion } = useRequest(SystemApi.getStableVersion);
const { data: latestDevVersion } = useRequest(SystemApi.getDevVersion);
const STABLE_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/';
const DEV_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/latest/';
const STABLE_RELNOTES_URL =
'https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md';
const DEV_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/latest/';
const DEV_RELNOTES_URL =
'https://github.com/emsesp/EMS-ESP32/blob/dev/CHANGELOG_LATEST.md';
@@ -95,16 +93,11 @@ const UploadDownload: FC = () => {
loading: isUploading,
uploading: progress,
send: sendUpload,
onSuccess: onSuccessUpload,
abort: cancelUpload
} = useRequest(SystemApi.uploadFile, {
immediate: false,
force: true
});
onSuccessUpload(({ data }) => {
immediate: false
}).onSuccess(({ data }) => {
if (data) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
setMd5(data.md5);
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
} else {
@@ -137,22 +130,6 @@ const UploadDownload: FC = () => {
toast.info(LL.DOWNLOAD_SUCCESSFUL());
};
onSuccessGetSettings((event) => {
saveFile(event.data, 'settings.json');
});
onSuccessGetCustomizations((event) => {
saveFile(event.data, 'customizations.json');
});
onSuccessGetEntities((event) => {
saveFile(event.data, 'entities.json');
});
onSuccessGetSchedule((event) => {
saveFile(event.data, 'schedule.json');
});
onGetAPI((event) => {
saveFile(event.data, event.args[0].device + '_' + event.args[0].entity + '.txt');
});
const downloadSettings = async () => {
await getSettings().catch((error: Error) => {
toast.error(error.message);
@@ -211,11 +188,7 @@ const UploadDownload: FC = () => {
<Link
target="_blank"
href={
STABLE_URL +
'v' +
latestVersion +
'/' +
getBinURL(latestVersion as string)
STABLE_URL + 'v' + latestVersion + '/' + getBinURL(latestVersion)
}
color="primary"
>
@@ -236,7 +209,7 @@ const UploadDownload: FC = () => {
)&nbsp;(
<Link
target="_blank"
href={DEV_URL + getBinURL(latestDevVersion as string)}
href={DEV_URL + getBinURL(latestDevVersion)}
color="primary"
>
{LL.DOWNLOAD(1)}

View File

@@ -1,5 +1,4 @@
import { useCallback, useState } from 'react';
import type { FC } from 'react';
import { Navigate, Route, Routes, useNavigate } from 'react-router-dom';
import { Tab } from '@mui/material';
@@ -12,7 +11,7 @@ import NetworkSettings from './NetworkSettings';
import { WiFiConnectionContext } from './WiFiConnectionContext';
import WiFiNetworkScanner from './WiFiNetworkScanner';
const Network: FC = () => {
const Network = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.SETTINGS_OF(LL.NETWORK(0)));

View File

@@ -1,5 +1,4 @@
import { useContext, useEffect, useState } from 'react';
import type { FC } from 'react';
import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel';
@@ -48,7 +47,7 @@ import RestartMonitor from '../../status/RestartMonitor';
import { WiFiConnectionContext } from './WiFiConnectionContext';
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
const NetworkSettings: FC = () => {
const NetworkSettings = () => {
const { LL } = useI18nContext();
const { selectedNetwork, deselectNetwork } = useContext(WiFiConnectionContext);
@@ -80,19 +79,22 @@ const NetworkSettings: FC = () => {
useEffect(() => {
if (!initialized && data) {
if (selectedNetwork) {
updateState('networkSettings', (current_data: NetworkSettingsType) => ({
ssid: selectedNetwork.ssid,
bssid: selectedNetwork.bssid,
password: current_data ? current_data.password : '',
hostname: current_data?.hostname,
static_ip_config: false,
bandwidth20: false,
tx_power: 0,
nosleep: false,
enableMDNS: true,
enableCORS: false,
CORSOrigin: '*'
}));
void updateState(
NetworkApi.readNetworkSettings(),
(current_data: NetworkSettingsType) => ({
ssid: selectedNetwork.ssid,
bssid: selectedNetwork.bssid,
password: current_data ? current_data.password : '',
hostname: current_data?.hostname,
static_ip_config: false,
bandwidth20: false,
tx_power: 0,
nosleep: false,
enableMDNS: true,
enableCORS: false,
CORSOrigin: '*'
})
);
}
setInitialized(true);
}

View File

@@ -1,5 +1,4 @@
import { useRef, useState } from 'react';
import type { FC } from 'react';
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
import { Button } from '@mui/material';
@@ -15,23 +14,28 @@ import WiFiNetworkSelector from './WiFiNetworkSelector';
const NUM_POLLS = 10;
const POLLING_FREQUENCY = 1000;
const WiFiNetworkScanner: FC = () => {
const WiFiNetworkScanner = () => {
const pollCount = useRef(0);
const { LL } = useI18nContext();
const [errorMessage, setErrorMessage] = useState<string>();
const { send: scanNetworks, onComplete: onCompleteScanNetworks } = useRequest(
NetworkApi.scanNetworks
); // is called on page load to start network scan
const {
data: networkList,
send: getNetworkList,
onSuccess: onSuccessNetworkList
} = useRequest(NetworkApi.listNetworks, {
immediate: false
});
// is called on page load to start network scan
const { send: scanNetworks } = useRequest(NetworkApi.scanNetworks).onComplete(
() => {
pollCount.current = 0;
setErrorMessage(undefined);
void updateState(NetworkApi.listNetworks(), () => undefined);
void getNetworkList();
}
);
onSuccessNetworkList((event) => {
const { data: networkList, send: getNetworkList } = useRequest(
NetworkApi.listNetworks,
{
immediate: false
}
).onSuccess((event) => {
// is called when network scan is completed
if (!event.data) {
const completedPollCount = pollCount.current + 1;
if (completedPollCount < NUM_POLLS) {
@@ -44,14 +48,6 @@ const WiFiNetworkScanner: FC = () => {
}
});
onCompleteScanNetworks(() => {
pollCount.current = 0;
setErrorMessage(undefined);
// TODO fix
updateState('listNetworks', () => undefined);
void getNetworkList();
});
const renderNetworkScanner = () => {
if (!networkList) {
return (

View File

@@ -1,5 +1,4 @@
import { useContext } from 'react';
import type { FC } from 'react';
import LockIcon from '@mui/icons-material/Lock';
import LockOpenIcon from '@mui/icons-material/LockOpen';
@@ -18,15 +17,11 @@ import type { Theme } from '@mui/material';
import { MessageBox } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { WiFiNetwork, WiFiNetworkList } from 'types';
import type { WiFiNetwork } from 'types';
import { WiFiEncryptionType } from 'types';
import { WiFiConnectionContext } from './WiFiConnectionContext';
interface WiFiNetworkSelectorProps {
networkList: WiFiNetworkList;
}
export const isNetworkOpen = ({ encryption_type }: WiFiNetwork) =>
encryption_type === WiFiEncryptionType.WIFI_AUTH_OPEN;
@@ -62,7 +57,7 @@ const networkQualityHighlight = ({ rssi }: WiFiNetwork, theme: Theme) => {
return theme.palette.success.main;
};
const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => {
function WiFiNetworkSelector({ networkList }) {
const { LL } = useI18nContext();
const theme = useTheme();
@@ -100,6 +95,6 @@ const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => {
}
return <List>{networkList.networks.map(renderNetwork)}</List>;
};
}
export default WiFiNetworkSelector;

View File

@@ -1,5 +1,4 @@
import { useEffect } from 'react';
import type { FC } from 'react';
import CloseIcon from '@mui/icons-material/Close';
import {
@@ -21,12 +20,7 @@ import { useRequest } from 'alova/client';
import { MessageBox } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
interface GenerateTokenProps {
username?: string;
onClose: () => void;
}
const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
export default function GenerateToken({ username, onClose }) {
const { LL } = useI18nContext();
const open = !!username;
@@ -85,6 +79,4 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
</DialogActions>
</Dialog>
);
};
export default GenerateToken;
}

View File

@@ -1,5 +1,4 @@
import { useContext, useState } from 'react';
import type { FC } from 'react';
import { useBlocker } from 'react-router-dom';
import CancelIcon from '@mui/icons-material/Cancel';
@@ -40,7 +39,7 @@ import { createUserValidator } from 'validators';
import GenerateToken from './GenerateToken';
import User from './User';
const ManageUsers: FC = () => {
const ManageUsers = () => {
const { loadData, saveData, saving, data, updateDataValue, errorMessage } =
useRest<SecuritySettingsType>({
read: SecurityApi.readSecuritySettings,

View File

@@ -1,4 +1,3 @@
import type { FC } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { Tab } from '@mui/material';
@@ -9,7 +8,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import ManageUsers from './ManageUsers';
import SecuritySettings from './SecuritySettings';
const Security: FC = () => {
const Security = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.SETTINGS_OF(LL.SECURITY(0)));

View File

@@ -1,5 +1,4 @@
import { useContext, useState } from 'react';
import type { FC } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning';
@@ -22,7 +21,7 @@ import type { SecuritySettingsType } from 'types';
import { updateValueDirty, useRest } from 'utils';
import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators';
const SecuritySettings: FC = () => {
const SecuritySettings = () => {
const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();

View File

@@ -29,10 +29,8 @@ import { validate } from 'validators';
interface UserFormProps {
creating: boolean;
validator: Schema;
user?: UserType;
setUser: React.Dispatch<React.SetStateAction<UserType | undefined>>;
onDoneEditing: () => void;
onCancelEditing: () => void;
}