mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 15:59:52 +03:00
alova 3
This commit is contained in:
@@ -21,6 +21,7 @@ export default tseslint.config(
|
|||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/no-unsafe-enum-comparison': 'off',
|
'@typescript-eslint/no-unsafe-enum-comparison': 'off',
|
||||||
|
'@typescript-eslint/no-unused-expressions': 'off',
|
||||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||||
'@typescript-eslint/no-misused-promises': [
|
'@typescript-eslint/no-misused-promises': [
|
||||||
'error',
|
'error',
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
|
"preact": "^10.23.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
@@ -57,14 +58,13 @@
|
|||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
"eslint": "^9.8.0",
|
"eslint": "^9.8.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"preact": "^10.23.1",
|
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"rollup-plugin-visualizer": "^5.12.0",
|
"rollup-plugin-visualizer": "^5.12.0",
|
||||||
"terser": "^5.31.3",
|
"terser": "^5.31.5",
|
||||||
"typescript-eslint": "8.0.1",
|
"typescript-eslint": "8.0.1",
|
||||||
"vite": "^5.3.5",
|
"vite": "^5.4.0",
|
||||||
"vite-plugin-imagemin": "^0.6.1",
|
"vite-plugin-imagemin": "^0.6.1",
|
||||||
"vite-tsconfig-paths": "^4.3.2"
|
"vite-tsconfig-paths": "^5.0.0"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.2.1"
|
"packageManager": "yarn@4.2.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
import { Slide, ToastContainer } from 'react-toastify';
|
import { Slide, ToastContainer } from 'react-toastify';
|
||||||
import 'react-toastify/dist/ReactToastify.min.css';
|
import 'react-toastify/dist/ReactToastify.min.css';
|
||||||
|
|
||||||
@@ -12,7 +11,7 @@ import { localStorageDetector } from 'typesafe-i18n/detectors';
|
|||||||
|
|
||||||
const detectedLocale = detectLocale(localStorageDetector);
|
const detectedLocale = detectLocale(localStorageDetector);
|
||||||
|
|
||||||
const App: FC = () => {
|
const App = () => {
|
||||||
const [wasLoaded, setWasLoaded] = useState(false);
|
const [wasLoaded, setWasLoaded] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const RemoveTrailingSlashes = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AppRouting: FC = () => {
|
const AppRouting = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { type FC, useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
import CustomEntities from 'app/main/CustomEntities';
|
import CustomEntities from 'app/main/CustomEntities';
|
||||||
import Customization from 'app/main/Customization';
|
import Customizations from 'app/main/Customizations';
|
||||||
import Devices from 'app/main/Devices';
|
import Devices from 'app/main/Devices';
|
||||||
import Help from 'app/main/Help';
|
import Help from 'app/main/Help';
|
||||||
import Modules from 'app/main/Modules';
|
import Modules from 'app/main/Modules';
|
||||||
@@ -27,7 +27,7 @@ import SystemLog from 'app/status/SystemLog';
|
|||||||
import { Layout } from 'components';
|
import { Layout } from 'components';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
|
||||||
const AuthenticatedRouting: FC = () => {
|
const AuthenticatedRouting = () => {
|
||||||
const { me } = useContext(AuthenticatedContext);
|
const { me } = useContext(AuthenticatedContext);
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
@@ -59,7 +59,7 @@ const AuthenticatedRouting: FC = () => {
|
|||||||
<Route path="/settings/network/*" element={<Network />} />
|
<Route path="/settings/network/*" element={<Network />} />
|
||||||
<Route path="/settings/security/*" element={<Security />} />
|
<Route path="/settings/security/*" element={<Security />} />
|
||||||
|
|
||||||
<Route path="/customizations" element={<Customization />} />
|
<Route path="/customizations" element={<Customizations />} />
|
||||||
<Route path="/scheduler" element={<Scheduler />} />
|
<Route path="/scheduler" element={<Scheduler />} />
|
||||||
<Route path="/customentities" element={<CustomEntities />} />
|
<Route path="/customentities" element={<CustomEntities />} />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import ForwardIcon from '@mui/icons-material/Forward';
|
import ForwardIcon from '@mui/icons-material/Forward';
|
||||||
@@ -21,7 +20,7 @@ import type { SignInRequest } from 'types';
|
|||||||
import { onEnterCallback, updateValue } from 'utils';
|
import { onEnterCallback, updateValue } from 'utils';
|
||||||
import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators';
|
import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators';
|
||||||
|
|
||||||
const SignIn: FC = () => {
|
const SignIn = () => {
|
||||||
const authenticationContext = useContext(AuthenticationContext);
|
const authenticationContext = useContext(AuthenticationContext);
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
@@ -33,14 +32,12 @@ const SignIn: FC = () => {
|
|||||||
const [processing, setProcessing] = useState<boolean>(false);
|
const [processing, setProcessing] = useState<boolean>(false);
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
|
||||||
const { send: callSignIn, onSuccess } = useRequest(
|
const { send: callSignIn } = useRequest(
|
||||||
(request: SignInRequest) => AuthenticationApi.signIn(request),
|
(request: SignInRequest) => AuthenticationApi.signIn(request),
|
||||||
{
|
{
|
||||||
immediate: false
|
immediate: false
|
||||||
}
|
}
|
||||||
);
|
).onSuccess((response) => {
|
||||||
|
|
||||||
onSuccess((response) => {
|
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
authenticationContext.signIn(response.data.access_token);
|
authenticationContext.signIn(response.data.access_token);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,9 @@ export const readNetworkStatus = () =>
|
|||||||
export const scanNetworks = () => alovaInstance.Get('/rest/scanNetworks');
|
export const scanNetworks = () => alovaInstance.Get('/rest/scanNetworks');
|
||||||
export const listNetworks = () =>
|
export const listNetworks = () =>
|
||||||
alovaInstance.Get<WiFiNetworkList>('/rest/listNetworks', {
|
alovaInstance.Get<WiFiNetworkList>('/rest/listNetworks', {
|
||||||
name: 'listNetworks',
|
|
||||||
timeout: 20000 // timeout 20 seconds
|
timeout: 20000 // timeout 20 seconds
|
||||||
});
|
});
|
||||||
export const readNetworkSettings = () =>
|
export const readNetworkSettings = () =>
|
||||||
alovaInstance.Get<NetworkSettingsType>('/rest/networkSettings', {
|
alovaInstance.Get<NetworkSettingsType>('/rest/networkSettings');
|
||||||
name: 'networkSettings'
|
|
||||||
});
|
|
||||||
export const updateNetworkSettings = (wifiSettings: NetworkSettingsType) =>
|
export const updateNetworkSettings = (wifiSettings: NetworkSettingsType) =>
|
||||||
alovaInstance.Post<NetworkSettingsType>('/rest/networkSettings', wifiSettings);
|
alovaInstance.Post<NetworkSettingsType>('/rest/networkSettings', wifiSettings);
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ import { alovaInstance } from './endpoints';
|
|||||||
|
|
||||||
export const readNTPStatus = () =>
|
export const readNTPStatus = () =>
|
||||||
alovaInstance.Get<NTPStatusType>('/rest/ntpStatus');
|
alovaInstance.Get<NTPStatusType>('/rest/ntpStatus');
|
||||||
|
|
||||||
export const readNTPSettings = () =>
|
export const readNTPSettings = () =>
|
||||||
alovaInstance.Get<NTPSettingsType>('/rest/ntpSettings', {
|
alovaInstance.Get<NTPSettingsType>('/rest/ntpSettings', {});
|
||||||
name: 'ntpSettings'
|
|
||||||
});
|
|
||||||
export const updateNTPSettings = (data: NTPSettingsType) =>
|
export const updateNTPSettings = (data: NTPSettingsType) =>
|
||||||
alovaInstance.Post<NTPSettingsType>('/rest/ntpSettings', data);
|
alovaInstance.Post<NTPSettingsType>('/rest/ntpSettings', data);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
import { useBlocker } from 'react-router-dom';
|
import { useBlocker } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
@@ -30,13 +29,13 @@ import {
|
|||||||
} from 'components';
|
} from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
import * as EMSESP from './api';
|
|
||||||
import SettingsCustomEntitiesDialog from './CustomEntitiesDialog';
|
import SettingsCustomEntitiesDialog from './CustomEntitiesDialog';
|
||||||
|
import { readCustomEntities, writeCustomEntities } from './api';
|
||||||
import { DeviceValueTypeNames, DeviceValueUOM_s } from './types';
|
import { DeviceValueTypeNames, DeviceValueUOM_s } from './types';
|
||||||
import type { Entities, EntityItem } from './types';
|
import type { Entities, EntityItem } from './types';
|
||||||
import { entityItemValidation } from './validators';
|
import { entityItemValidation } from './validators';
|
||||||
|
|
||||||
const CustomEntities: FC = () => {
|
const CustomEntities = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const [numChanges, setNumChanges] = useState<number>(0);
|
const [numChanges, setNumChanges] = useState<number>(0);
|
||||||
const blocker = useBlocker(numChanges !== 0);
|
const blocker = useBlocker(numChanges !== 0);
|
||||||
@@ -50,13 +49,12 @@ const CustomEntities: FC = () => {
|
|||||||
data: entities,
|
data: entities,
|
||||||
send: fetchEntities,
|
send: fetchEntities,
|
||||||
error
|
error
|
||||||
} = useRequest(EMSESP.readCustomEntities, {
|
} = useRequest(readCustomEntities, {
|
||||||
initialData: [],
|
initialData: []
|
||||||
force: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { send: writeEntities } = useRequest(
|
const { send: writeEntities } = useRequest(
|
||||||
(data: Entities) => EMSESP.writeCustomEntities(data),
|
(data: Entities) => writeCustomEntities(data),
|
||||||
{ immediate: false }
|
{ immediate: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -182,7 +180,7 @@ const CustomEntities: FC = () => {
|
|||||||
|
|
||||||
const onDialogSave = (updatedItem: EntityItem) => {
|
const onDialogSave = (updatedItem: EntityItem) => {
|
||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
updateState('entities', (data: EntityItem[]) => {
|
void updateState(readCustomEntities(), (data: EntityItem[]) => {
|
||||||
const new_data = creating
|
const new_data = creating
|
||||||
? [
|
? [
|
||||||
...data.filter((ei) => creating || ei.o_id !== updatedItem.o_id),
|
...data.filter((ei) => creating || ei.o_id !== updatedItem.o_id),
|
||||||
|
|||||||
@@ -291,7 +291,11 @@ const CustomEntitiesDialog = ({
|
|||||||
fullWidth
|
fullWidth
|
||||||
margin="normal"
|
margin="normal"
|
||||||
type="number"
|
type="number"
|
||||||
inputProps={{ min: '1', max: String(256 - editItem.offset), step: '1' }}
|
inputProps={{
|
||||||
|
min: '1',
|
||||||
|
max: String(256 - editItem.offset),
|
||||||
|
step: '1'
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
import { useBlocker, useLocation } from 'react-router-dom';
|
import { useBlocker, useLocation } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
@@ -52,7 +51,7 @@ import {
|
|||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
import * as EMSESP from './api';
|
import * as EMSESP from './api';
|
||||||
import SettingsCustomizationDialog from './CustomizationDialog';
|
import SettingsCustomizationsDialog from './CustomizationsDialog';
|
||||||
import EntityMaskToggle from './EntityMaskToggle';
|
import EntityMaskToggle from './EntityMaskToggle';
|
||||||
import OptionIcon from './OptionIcon';
|
import OptionIcon from './OptionIcon';
|
||||||
import { DeviceEntityMask } from './types';
|
import { DeviceEntityMask } from './types';
|
||||||
@@ -60,7 +59,7 @@ import type { DeviceEntity, DeviceShort } from './types';
|
|||||||
|
|
||||||
export const APIURL = window.location.origin + '/api/';
|
export const APIURL = window.location.origin + '/api/';
|
||||||
|
|
||||||
const Customization: FC = () => {
|
const Customizations = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const [numChanges, setNumChanges] = useState<number>(0);
|
const [numChanges, setNumChanges] = useState<number>(0);
|
||||||
const blocker = useBlocker(numChanges !== 0);
|
const blocker = useBlocker(numChanges !== 0);
|
||||||
@@ -106,13 +105,15 @@ const Customization: FC = () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const { send: readDeviceEntities, onSuccess: onSuccess } = useRequest(
|
const { send: readDeviceEntities } = useRequest(
|
||||||
(data: number) => EMSESP.readDeviceEntities(data),
|
(data: number) => EMSESP.readDeviceEntities(data),
|
||||||
{
|
{
|
||||||
initialData: [],
|
initialData: [],
|
||||||
immediate: false
|
immediate: false
|
||||||
}
|
}
|
||||||
);
|
).onSuccess((event) => {
|
||||||
|
setOriginalSettings(event.data);
|
||||||
|
});
|
||||||
|
|
||||||
const setOriginalSettings = (data: DeviceEntity[]) => {
|
const setOriginalSettings = (data: DeviceEntity[]) => {
|
||||||
setDeviceEntities(
|
setDeviceEntities(
|
||||||
@@ -126,10 +127,6 @@ const Customization: FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
onSuccess((event) => {
|
|
||||||
setOriginalSettings(event.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
||||||
immediate: false
|
immediate: false
|
||||||
});
|
});
|
||||||
@@ -727,7 +724,7 @@ const Customization: FC = () => {
|
|||||||
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||||
{restarting ? <RestartMonitor /> : renderContent()}
|
{restarting ? <RestartMonitor /> : renderContent()}
|
||||||
{selectedDeviceEntity && (
|
{selectedDeviceEntity && (
|
||||||
<SettingsCustomizationDialog
|
<SettingsCustomizationsDialog
|
||||||
open={dialogOpen}
|
open={dialogOpen}
|
||||||
onClose={onDialogClose}
|
onClose={onDialogClose}
|
||||||
onSave={onDialogSave}
|
onSave={onDialogSave}
|
||||||
@@ -738,4 +735,4 @@ const Customization: FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Customization;
|
export default Customizations;
|
||||||
@@ -23,19 +23,19 @@ import EntityMaskToggle from './EntityMaskToggle';
|
|||||||
import { DeviceEntityMask } from './types';
|
import { DeviceEntityMask } from './types';
|
||||||
import type { DeviceEntity } from './types';
|
import type { DeviceEntity } from './types';
|
||||||
|
|
||||||
interface SettingsCustomizationDialogProps {
|
interface SettingsCustomizationsDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSave: (di: DeviceEntity) => void;
|
onSave: (di: DeviceEntity) => void;
|
||||||
selectedItem: DeviceEntity;
|
selectedItem: DeviceEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomizationDialog = ({
|
const CustomizationsDialog = ({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
onSave,
|
onSave,
|
||||||
selectedItem
|
selectedItem
|
||||||
}: SettingsCustomizationDialogProps) => {
|
}: SettingsCustomizationsDialogProps) => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const [editItem, setEditItem] = useState<DeviceEntity>(selectedItem);
|
const [editItem, setEditItem] = useState<DeviceEntity>(selectedItem);
|
||||||
const [error, setError] = useState<boolean>(false);
|
const [error, setError] = useState<boolean>(false);
|
||||||
@@ -175,4 +175,4 @@ const CustomizationDialog = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CustomizationDialog;
|
export default CustomizationsDialog;
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/ai';
|
import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/ai';
|
||||||
import { CgSmartHomeBoiler } from 'react-icons/cg';
|
import { CgSmartHomeBoiler } from 'react-icons/cg';
|
||||||
import { FaSolarPanel } from 'react-icons/fa';
|
import { FaSolarPanel } from 'react-icons/fa';
|
||||||
@@ -16,11 +15,7 @@ import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
|
|||||||
|
|
||||||
import { DeviceType } from './types';
|
import { DeviceType } from './types';
|
||||||
|
|
||||||
interface DeviceIconProps {
|
export default function DeviceIcon({ type_id }) {
|
||||||
type_id: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
|
|
||||||
switch (type_id as DeviceType) {
|
switch (type_id as DeviceType) {
|
||||||
case DeviceType.TEMPERATURESENSOR:
|
case DeviceType.TEMPERATURESENSOR:
|
||||||
case DeviceType.ANALOGSENSOR:
|
case DeviceType.ANALOGSENSOR:
|
||||||
@@ -60,6 +55,4 @@ const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
|
|||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default DeviceIcon;
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
useState
|
useState
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
import { IconContext } from 'react-icons';
|
import { IconContext } from 'react-icons';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -70,7 +69,7 @@ import { DeviceEntityMask, DeviceType, DeviceValueUOM_s } from './types';
|
|||||||
import type { Device, DeviceValue } from './types';
|
import type { Device, DeviceValue } from './types';
|
||||||
import { deviceValueItemValidation } from './validators';
|
import { deviceValueItemValidation } from './validators';
|
||||||
|
|
||||||
const Devices: FC = () => {
|
const Devices = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const { me } = useContext(AuthenticatedContext);
|
const { me } = useContext(AuthenticatedContext);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import CommentIcon from '@mui/icons-material/CommentTwoTone';
|
import CommentIcon from '@mui/icons-material/CommentTwoTone';
|
||||||
@@ -21,27 +20,19 @@ import {
|
|||||||
import * as SystemApi from 'api/system';
|
import * as SystemApi from 'api/system';
|
||||||
|
|
||||||
import * as EMSESP from 'app/main/api';
|
import * as EMSESP from 'app/main/api';
|
||||||
import { useAutoRequest, useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import { SectionContent, useLayoutTitle } from 'components';
|
import { SectionContent, useLayoutTitle } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
import type { APIcall } from './types';
|
import type { APIcall } from './types';
|
||||||
|
|
||||||
const Help: FC = () => {
|
const Help = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle(LL.HELP_OF(''));
|
useLayoutTitle(LL.HELP_OF(''));
|
||||||
|
|
||||||
const { send: getAPI, onSuccess: onGetAPI } = useRequest(
|
const { send: getAPI } = useRequest((data: APIcall) => EMSESP.API(data), {
|
||||||
(data: APIcall) => EMSESP.API(data),
|
|
||||||
{
|
|
||||||
immediate: false
|
immediate: false
|
||||||
}
|
}).onSuccess((event) => {
|
||||||
);
|
|
||||||
|
|
||||||
// TODO check useAutoRequest - https://alova.js.org/tutorial/client/strategy/use-auto-request/#basic-usage
|
|
||||||
const { data, loading } = useAutoRequest(SystemApi.readSystemStatus);
|
|
||||||
|
|
||||||
onGetAPI((event) => {
|
|
||||||
const anchor = document.createElement('a');
|
const anchor = document.createElement('a');
|
||||||
anchor.href = URL.createObjectURL(
|
anchor.href = URL.createObjectURL(
|
||||||
new Blob([JSON.stringify(event.data, null, 2)], {
|
new Blob([JSON.stringify(event.data, null, 2)], {
|
||||||
@@ -56,14 +47,16 @@ const Help: FC = () => {
|
|||||||
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data, loading } = useRequest(SystemApi.readSystemStatus);
|
||||||
|
|
||||||
const callAPI = async (device: string, entity: string) => {
|
const callAPI = async (device: string, entity: string) => {
|
||||||
await getAPI({ device, entity, id: 0 }).catch((error: Error) => {
|
await getAPI({ device, entity, id: 0 }).catch((error: Error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO remove debug testing useRequest preact hook
|
||||||
console.log('loading: ' + loading + ' data2: ' + data);
|
console.log('loading: ' + loading + ' data2: ' + data);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
import { useBlocker } from 'react-router-dom';
|
import { useBlocker } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
@@ -8,8 +7,6 @@ import CircleIcon from '@mui/icons-material/Circle';
|
|||||||
import WarningIcon from '@mui/icons-material/Warning';
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
import { Box, Button, Typography } from '@mui/material';
|
import { Box, Button, Typography } from '@mui/material';
|
||||||
|
|
||||||
import { alovaInstance } from 'api/endpoints';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Cell,
|
Cell,
|
||||||
@@ -30,11 +27,11 @@ import {
|
|||||||
} from 'components';
|
} from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
import * as EMSESP from './api';
|
|
||||||
import ModulesDialog from './ModulesDialog';
|
import ModulesDialog from './ModulesDialog';
|
||||||
|
import { readModules, writeModules } from './api';
|
||||||
import type { ModuleItem, Modules } from './types';
|
import type { ModuleItem, Modules } from './types';
|
||||||
|
|
||||||
const Modules: FC = () => {
|
const Modules = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const [numChanges, setNumChanges] = useState<number>(0);
|
const [numChanges, setNumChanges] = useState<number>(0);
|
||||||
const blocker = useBlocker(numChanges !== 0);
|
const blocker = useBlocker(numChanges !== 0);
|
||||||
@@ -46,13 +43,12 @@ const Modules: FC = () => {
|
|||||||
data: modules,
|
data: modules,
|
||||||
send: fetchModules,
|
send: fetchModules,
|
||||||
error
|
error
|
||||||
} = useRequest(EMSESP.readModules, {
|
} = useRequest(readModules, {
|
||||||
initialData: []
|
initialData: []
|
||||||
});
|
});
|
||||||
|
|
||||||
const { send: writeModules } = useRequest(
|
const { send: updateModules } = useRequest(
|
||||||
(data: { key: string; enabled: boolean; license: string }) =>
|
(data: { key: string; enabled: boolean; license: string }) => writeModules(data),
|
||||||
EMSESP.writeModules(data),
|
|
||||||
{
|
{
|
||||||
immediate: false
|
immediate: false
|
||||||
}
|
}
|
||||||
@@ -124,23 +120,18 @@ const Modules: FC = () => {
|
|||||||
return mi.enabled !== mi.o_enabled || mi.license !== mi.o_license;
|
return mi.enabled !== mi.o_enabled || mi.license !== mi.o_license;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO example of how to use updateState
|
|
||||||
// TODO see https://alova.js.org/api/states/#updatestate
|
|
||||||
const updateModuleItem = (updatedItem: ModuleItem) => {
|
const updateModuleItem = (updatedItem: ModuleItem) => {
|
||||||
updateState<ModuleItem[]>(
|
void updateState(readModules(), (data: ModuleItem[]) => {
|
||||||
[alovaInstance.snapshots.match('modules', true)] as any,
|
|
||||||
(data: ModuleItem[]) => {
|
|
||||||
const new_data = data.map((mi) =>
|
const new_data = data.map((mi) =>
|
||||||
mi.id === updatedItem.id ? { ...mi, ...updatedItem } : mi
|
mi.id === updatedItem.id ? { ...mi, ...updatedItem } : mi
|
||||||
);
|
);
|
||||||
setNumChanges(new_data.filter((mi) => hasModulesChanged(mi)).length);
|
setNumChanges(new_data.filter((mi) => hasModulesChanged(mi)).length);
|
||||||
return new_data;
|
return new_data;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveModules = async () => {
|
const saveModules = async () => {
|
||||||
await writeModules({
|
await updateModules({
|
||||||
modules: modules.map((condensed_mi) => ({
|
modules: modules.map((condensed_mi) => ({
|
||||||
key: condensed_mi.key,
|
key: condensed_mi.key,
|
||||||
enabled: condensed_mi.enabled,
|
enabled: condensed_mi.enabled,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
||||||
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||||
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||||
@@ -32,18 +30,11 @@ const OPTION_ICONS: {
|
|||||||
favorite: [StarIcon, StarOutlineIcon]
|
favorite: [StarIcon, StarOutlineIcon]
|
||||||
};
|
};
|
||||||
|
|
||||||
interface OptionIconProps {
|
export default function OptionIcon({ type, isSet }) {
|
||||||
type: OptionType;
|
|
||||||
isSet: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const OptionIcon: FC<OptionIconProps> = ({ type, isSet }) => {
|
|
||||||
const Icon = OPTION_ICONS[type][isSet ? 0 : 1];
|
const Icon = OPTION_ICONS[type][isSet ? 0 : 1];
|
||||||
return isSet ? (
|
return isSet ? (
|
||||||
<Icon color="primary" sx={{ fontSize: 16, verticalAlign: 'middle' }} />
|
<Icon color="primary" sx={{ fontSize: 16, verticalAlign: 'middle' }} />
|
||||||
) : (
|
) : (
|
||||||
<Icon sx={{ fontSize: 16, verticalAlign: 'middle' }} />
|
<Icon sx={{ fontSize: 16, verticalAlign: 'middle' }} />
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default OptionIcon;
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
import { useBlocker } from 'react-router-dom';
|
import { useBlocker } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
@@ -29,13 +28,13 @@ import {
|
|||||||
} from 'components';
|
} from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
import * as EMSESP from './api';
|
|
||||||
import SettingsSchedulerDialog from './SchedulerDialog';
|
import SettingsSchedulerDialog from './SchedulerDialog';
|
||||||
|
import { readSchedule, writeSchedule } from './api';
|
||||||
import { ScheduleFlag } from './types';
|
import { ScheduleFlag } from './types';
|
||||||
import type { Schedule, ScheduleItem } from './types';
|
import type { Schedule, ScheduleItem } from './types';
|
||||||
import { schedulerItemValidation } from './validators';
|
import { schedulerItemValidation } from './validators';
|
||||||
|
|
||||||
const Scheduler: FC = () => {
|
const Scheduler = () => {
|
||||||
const { LL, locale } = useI18nContext();
|
const { LL, locale } = useI18nContext();
|
||||||
const [numChanges, setNumChanges] = useState<number>(0);
|
const [numChanges, setNumChanges] = useState<number>(0);
|
||||||
const blocker = useBlocker(numChanges !== 0);
|
const blocker = useBlocker(numChanges !== 0);
|
||||||
@@ -48,13 +47,12 @@ const Scheduler: FC = () => {
|
|||||||
data: schedule,
|
data: schedule,
|
||||||
send: fetchSchedule,
|
send: fetchSchedule,
|
||||||
error
|
error
|
||||||
} = useRequest(EMSESP.readSchedule, {
|
} = useRequest(readSchedule, {
|
||||||
initialData: [],
|
initialData: []
|
||||||
force: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { send: writeSchedule } = useRequest(
|
const { send: updateSchedule } = useRequest(
|
||||||
(data: Schedule) => EMSESP.writeSchedule(data),
|
(data: Schedule) => writeSchedule(data),
|
||||||
{
|
{
|
||||||
immediate: false
|
immediate: false
|
||||||
}
|
}
|
||||||
@@ -131,7 +129,7 @@ const Scheduler: FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const saveSchedule = async () => {
|
const saveSchedule = async () => {
|
||||||
await writeSchedule({
|
await updateSchedule({
|
||||||
schedule: schedule
|
schedule: schedule
|
||||||
.filter((si) => !si.deleted)
|
.filter((si) => !si.deleted)
|
||||||
.map((condensed_si) => ({
|
.map((condensed_si) => ({
|
||||||
@@ -177,7 +175,7 @@ const Scheduler: FC = () => {
|
|||||||
|
|
||||||
const onDialogSave = (updatedItem: ScheduleItem) => {
|
const onDialogSave = (updatedItem: ScheduleItem) => {
|
||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
updateState('schedule', (data: ScheduleItem[]) => {
|
void updateState(readSchedule(), (data: ScheduleItem[]) => {
|
||||||
const new_data = creating
|
const new_data = creating
|
||||||
? [
|
? [
|
||||||
...data.filter((si) => creating || si.o_id !== updatedItem.o_id),
|
...data.filter((si) => creating || si.o_id !== updatedItem.o_id),
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ const SchedulerDialog = ({
|
|||||||
scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE ? 'primary' : 'grey'
|
scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE ? 'primary' : 'grey'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{LL.ONCHANGE(0)}
|
{LL.ONCHANGE()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<ToggleButton value={ScheduleFlag.SCHEDULE_CONDITION}>
|
<ToggleButton value={ScheduleFlag.SCHEDULE_CONDITION}>
|
||||||
@@ -218,7 +218,7 @@ const SchedulerDialog = ({
|
|||||||
scheduleType === ScheduleFlag.SCHEDULE_CONDITION ? 'primary' : 'grey'
|
scheduleType === ScheduleFlag.SCHEDULE_CONDITION ? 'primary' : 'grey'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{LL.CONDITION(0)}
|
{LL.CONDITION()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<ToggleButton value={ScheduleFlag.SCHEDULE_IMMEDIATE}>
|
<ToggleButton value={ScheduleFlag.SCHEDULE_IMMEDIATE}>
|
||||||
@@ -228,7 +228,7 @@ const SchedulerDialog = ({
|
|||||||
scheduleType === ScheduleFlag.SCHEDULE_IMMEDIATE ? 'primary' : 'grey'
|
scheduleType === ScheduleFlag.SCHEDULE_IMMEDIATE ? 'primary' : 'grey'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{LL.IMMEDIATE(0)}
|
{LL.IMMEDIATE()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
@@ -311,10 +311,10 @@ const SchedulerDialog = ({
|
|||||||
name="time"
|
name="time"
|
||||||
label={
|
label={
|
||||||
scheduleType === ScheduleFlag.SCHEDULE_CONDITION
|
scheduleType === ScheduleFlag.SCHEDULE_CONDITION
|
||||||
? LL.CONDITION(1)
|
? LL.CONDITION()
|
||||||
: scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE
|
: scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE
|
||||||
? LL.ONCHANGE(1)
|
? LL.ONCHANGE()
|
||||||
: LL.IMMEDIATE(1)
|
: LL.IMMEDIATE()
|
||||||
}
|
}
|
||||||
multiline
|
multiline
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useContext, useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
|
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
|
||||||
@@ -46,7 +45,7 @@ import {
|
|||||||
temperatureSensorItemValidation
|
temperatureSensorItemValidation
|
||||||
} from './validators';
|
} from './validators';
|
||||||
|
|
||||||
const Sensors: FC = () => {
|
const Sensors = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const { me } = useContext(AuthenticatedContext);
|
const { me } = useContext(AuthenticatedContext);
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ export const writeDeviceName = (data: { id: number; name: string }) =>
|
|||||||
// SettingsScheduler
|
// SettingsScheduler
|
||||||
export const readSchedule = () =>
|
export const readSchedule = () =>
|
||||||
alovaInstance.Get<ScheduleItem[]>('/rest/schedule', {
|
alovaInstance.Get<ScheduleItem[]>('/rest/schedule', {
|
||||||
name: 'schedule',
|
|
||||||
transform(data) {
|
transform(data) {
|
||||||
return (data as Schedule).schedule.map((si: ScheduleItem) => ({
|
return (data as Schedule).schedule.map((si: ScheduleItem) => ({
|
||||||
...si,
|
...si,
|
||||||
@@ -109,7 +108,6 @@ export const writeSchedule = (data: Schedule) =>
|
|||||||
// Modules
|
// Modules
|
||||||
export const readModules = () =>
|
export const readModules = () =>
|
||||||
alovaInstance.Get<ModuleItem[]>('/rest/modules', {
|
alovaInstance.Get<ModuleItem[]>('/rest/modules', {
|
||||||
name: 'modules',
|
|
||||||
transform(data) {
|
transform(data) {
|
||||||
return (data as Modules).modules.map((mi: ModuleItem) => ({
|
return (data as Modules).modules.map((mi: ModuleItem) => ({
|
||||||
...mi,
|
...mi,
|
||||||
@@ -127,7 +125,6 @@ export const writeModules = (data: {
|
|||||||
// SettingsEntities
|
// SettingsEntities
|
||||||
export const readCustomEntities = () =>
|
export const readCustomEntities = () =>
|
||||||
alovaInstance.Get<EntityItem[]>('/rest/customEntities', {
|
alovaInstance.Get<EntityItem[]>('/rest/customEntities', {
|
||||||
name: 'entities',
|
|
||||||
transform(data) {
|
transform(data) {
|
||||||
return (data as Entities).entities.map((ei: EntityItem) => ({
|
return (data as Entities).entities.map((ei: EntityItem) => ({
|
||||||
...ei,
|
...ei,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
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_ALWAYS ||
|
||||||
provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
|
provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
|
||||||
|
|
||||||
const APSettings: FC = () => {
|
const APSettings = () => {
|
||||||
const {
|
const {
|
||||||
loadData,
|
loadData,
|
||||||
saving,
|
saving,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
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, {
|
const { data: hardwareData } = useRequest(SystemApi.readHardwareStatus, {
|
||||||
force: true
|
initialData: { psram: false }
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -84,19 +83,12 @@ const ApplicationSettings: FC = () => {
|
|||||||
|
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
|
||||||
const {
|
const { loading: processingBoard, send: readBoardProfile } = useRequest(
|
||||||
loading: processingBoard,
|
(boardProfile: string) => EMSESP.getBoardProfile(boardProfile),
|
||||||
send: readBoardProfile,
|
{
|
||||||
onSuccess: onSuccessBoardProfile
|
|
||||||
} = useRequest((boardProfile: string) => EMSESP.getBoardProfile(boardProfile), {
|
|
||||||
immediate: false
|
immediate: false
|
||||||
});
|
}
|
||||||
|
).onSuccess((event) => {
|
||||||
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
|
||||||
immediate: false
|
|
||||||
});
|
|
||||||
|
|
||||||
onSuccessBoardProfile((event) => {
|
|
||||||
const response = event.data as Settings;
|
const response = event.data as Settings;
|
||||||
updateDataValue({
|
updateDataValue({
|
||||||
...data,
|
...data,
|
||||||
@@ -113,6 +105,10 @@ const ApplicationSettings: FC = () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
const updateBoardProfile = async (board_profile: string) => {
|
const updateBoardProfile = async (board_profile: string) => {
|
||||||
await readBoardProfile(board_profile).catch((error: Error) => {
|
await readBoardProfile(board_profile).catch((error: Error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
@@ -31,7 +30,7 @@ import type { MqttSettingsType } from 'types';
|
|||||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||||
import { createMqttSettingsValidator, validate } from 'validators';
|
import { createMqttSettingsValidator, validate } from 'validators';
|
||||||
|
|
||||||
const MqttSettings: FC = () => {
|
const MqttSettings = () => {
|
||||||
const {
|
const {
|
||||||
loadData,
|
loadData,
|
||||||
saving,
|
saving,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
import { Button, Checkbox, MenuItem } from '@mui/material';
|
import { Button, Checkbox, MenuItem } from '@mui/material';
|
||||||
|
|
||||||
import * as NTPApi from 'api/ntp';
|
import * as NTPApi from 'api/ntp';
|
||||||
|
import { readNTPSettings } from 'api/ntp';
|
||||||
|
|
||||||
import { updateState } from 'alova/client';
|
import { updateState } from 'alova/client';
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
@@ -26,7 +26,7 @@ import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
|
|||||||
|
|
||||||
import { TIME_ZONES, selectedTimeZone, timeZoneSelectItems } from './TZ';
|
import { TIME_ZONES, selectedTimeZone, timeZoneSelectItems } from './TZ';
|
||||||
|
|
||||||
const NTPSettings: FC = () => {
|
const NTPSettings = () => {
|
||||||
const {
|
const {
|
||||||
loadData,
|
loadData,
|
||||||
saving,
|
saving,
|
||||||
@@ -72,9 +72,7 @@ const NTPSettings: FC = () => {
|
|||||||
|
|
||||||
const changeTimeZone = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const changeTimeZone = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
updateFormValue(event);
|
updateFormValue(event);
|
||||||
|
void updateState(readNTPSettings(), (settings: NTPSettingsType) => ({
|
||||||
// TODO fix
|
|
||||||
updateState('ntpSettings', (settings: NTPSettingsType) => ({
|
|
||||||
...settings,
|
...settings,
|
||||||
tz_label: event.target.value,
|
tz_label: event.target.value,
|
||||||
tz_format: TIME_ZONES[event.target.value]
|
tz_format: TIME_ZONES[event.target.value]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type FC, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
@@ -29,7 +29,7 @@ import { SectionContent, useLayoutTitle } from 'components';
|
|||||||
import ListMenuItem from 'components/layout/ListMenuItem';
|
import ListMenuItem from 'components/layout/ListMenuItem';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
const Settings: FC = () => {
|
const Settings = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle(LL.SETTINGS(0));
|
useLayoutTitle(LL.SETTINGS(0));
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type FC, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||||
@@ -19,61 +19,59 @@ import { useI18nContext } from 'i18n/i18n-react';
|
|||||||
|
|
||||||
import RestartMonitor from '../status/RestartMonitor';
|
import RestartMonitor from '../status/RestartMonitor';
|
||||||
|
|
||||||
const UploadDownload: FC = () => {
|
const UploadDownload = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const [restarting, setRestarting] = useState<boolean>();
|
const [restarting, setRestarting] = useState<boolean>();
|
||||||
const [md5, setMd5] = useState<string>();
|
const [md5, setMd5] = useState<string>();
|
||||||
|
|
||||||
const { send: getSettings, onSuccess: onSuccessGetSettings } = useRequest(
|
const { send: getSettings } = useRequest(EMSESP.getSettings(), {
|
||||||
EMSESP.getSettings(),
|
|
||||||
{
|
|
||||||
immediate: false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const { send: getCustomizations, onSuccess: onSuccessGetCustomizations } =
|
|
||||||
useRequest(EMSESP.getCustomizations(), {
|
|
||||||
immediate: false
|
immediate: false
|
||||||
|
}).onSuccess((event) => {
|
||||||
|
saveFile(event.data, 'settings.json');
|
||||||
});
|
});
|
||||||
const { send: getEntities, onSuccess: onSuccessGetEntities } = useRequest(
|
|
||||||
EMSESP.getEntities(),
|
const { send: getCustomizations } = useRequest(EMSESP.getCustomizations(), {
|
||||||
{
|
|
||||||
immediate: false
|
immediate: false
|
||||||
}
|
}).onSuccess((event) => {
|
||||||
);
|
saveFile(event.data, 'customizations.json');
|
||||||
const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest(
|
});
|
||||||
EMSESP.getSchedule(),
|
|
||||||
{
|
const { send: getEntities } = useRequest(EMSESP.getEntities(), {
|
||||||
immediate: false
|
immediate: false
|
||||||
}
|
}).onSuccess((event) => {
|
||||||
);
|
saveFile(event.data, 'entities.json');
|
||||||
const { send: getAPI, onSuccess: onGetAPI } = useRequest(
|
});
|
||||||
(data: APIcall) => EMSESP.API(data),
|
|
||||||
{
|
const { send: getSchedule } = useRequest(EMSESP.getSchedule(), {
|
||||||
immediate: false
|
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 {
|
const {
|
||||||
data: data,
|
data: data,
|
||||||
send: loadData,
|
send: loadData,
|
||||||
error
|
error
|
||||||
} = useRequest(SystemApi.readHardwareStatus, { force: true });
|
} = useRequest(SystemApi.readHardwareStatus);
|
||||||
|
|
||||||
const { data: latestVersion } = useRequest(SystemApi.getStableVersion, {
|
// called immediately to get the latest version, on page load
|
||||||
immediate: true,
|
const { data: latestVersion } = useRequest(SystemApi.getStableVersion);
|
||||||
force: true
|
const { data: latestDevVersion } = useRequest(SystemApi.getDevVersion);
|
||||||
});
|
|
||||||
|
|
||||||
const { data: latestDevVersion } = useRequest(SystemApi.getDevVersion, {
|
|
||||||
immediate: true,
|
|
||||||
force: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const STABLE_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/';
|
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 =
|
const STABLE_RELNOTES_URL =
|
||||||
'https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md';
|
'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 =
|
const DEV_RELNOTES_URL =
|
||||||
'https://github.com/emsesp/EMS-ESP32/blob/dev/CHANGELOG_LATEST.md';
|
'https://github.com/emsesp/EMS-ESP32/blob/dev/CHANGELOG_LATEST.md';
|
||||||
|
|
||||||
@@ -95,16 +93,11 @@ const UploadDownload: FC = () => {
|
|||||||
loading: isUploading,
|
loading: isUploading,
|
||||||
uploading: progress,
|
uploading: progress,
|
||||||
send: sendUpload,
|
send: sendUpload,
|
||||||
onSuccess: onSuccessUpload,
|
|
||||||
abort: cancelUpload
|
abort: cancelUpload
|
||||||
} = useRequest(SystemApi.uploadFile, {
|
} = useRequest(SystemApi.uploadFile, {
|
||||||
immediate: false,
|
immediate: false
|
||||||
force: true
|
}).onSuccess(({ data }) => {
|
||||||
});
|
|
||||||
|
|
||||||
onSuccessUpload(({ data }) => {
|
|
||||||
if (data) {
|
if (data) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
||||||
setMd5(data.md5);
|
setMd5(data.md5);
|
||||||
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
|
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
|
||||||
} else {
|
} else {
|
||||||
@@ -137,22 +130,6 @@ const UploadDownload: FC = () => {
|
|||||||
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
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 () => {
|
const downloadSettings = async () => {
|
||||||
await getSettings().catch((error: Error) => {
|
await getSettings().catch((error: Error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
@@ -211,11 +188,7 @@ const UploadDownload: FC = () => {
|
|||||||
<Link
|
<Link
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={
|
href={
|
||||||
STABLE_URL +
|
STABLE_URL + 'v' + latestVersion + '/' + getBinURL(latestVersion)
|
||||||
'v' +
|
|
||||||
latestVersion +
|
|
||||||
'/' +
|
|
||||||
getBinURL(latestVersion as string)
|
|
||||||
}
|
}
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
@@ -236,7 +209,7 @@ const UploadDownload: FC = () => {
|
|||||||
) (
|
) (
|
||||||
<Link
|
<Link
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={DEV_URL + getBinURL(latestDevVersion as string)}
|
href={DEV_URL + getBinURL(latestDevVersion)}
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
{LL.DOWNLOAD(1)}
|
{LL.DOWNLOAD(1)}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
import { Navigate, Route, Routes, useNavigate } from 'react-router-dom';
|
import { Navigate, Route, Routes, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { Tab } from '@mui/material';
|
import { Tab } from '@mui/material';
|
||||||
@@ -12,7 +11,7 @@ import NetworkSettings from './NetworkSettings';
|
|||||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||||
import WiFiNetworkScanner from './WiFiNetworkScanner';
|
import WiFiNetworkScanner from './WiFiNetworkScanner';
|
||||||
|
|
||||||
const Network: FC = () => {
|
const Network = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle(LL.SETTINGS_OF(LL.NETWORK(0)));
|
useLayoutTitle(LL.SETTINGS_OF(LL.NETWORK(0)));
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useContext, useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
@@ -48,7 +47,7 @@ import RestartMonitor from '../../status/RestartMonitor';
|
|||||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||||
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
|
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
|
||||||
|
|
||||||
const NetworkSettings: FC = () => {
|
const NetworkSettings = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const { selectedNetwork, deselectNetwork } = useContext(WiFiConnectionContext);
|
const { selectedNetwork, deselectNetwork } = useContext(WiFiConnectionContext);
|
||||||
@@ -80,7 +79,9 @@ const NetworkSettings: FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!initialized && data) {
|
if (!initialized && data) {
|
||||||
if (selectedNetwork) {
|
if (selectedNetwork) {
|
||||||
updateState('networkSettings', (current_data: NetworkSettingsType) => ({
|
void updateState(
|
||||||
|
NetworkApi.readNetworkSettings(),
|
||||||
|
(current_data: NetworkSettingsType) => ({
|
||||||
ssid: selectedNetwork.ssid,
|
ssid: selectedNetwork.ssid,
|
||||||
bssid: selectedNetwork.bssid,
|
bssid: selectedNetwork.bssid,
|
||||||
password: current_data ? current_data.password : '',
|
password: current_data ? current_data.password : '',
|
||||||
@@ -92,7 +93,8 @@ const NetworkSettings: FC = () => {
|
|||||||
enableMDNS: true,
|
enableMDNS: true,
|
||||||
enableCORS: false,
|
enableCORS: false,
|
||||||
CORSOrigin: '*'
|
CORSOrigin: '*'
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
setInitialized(true);
|
setInitialized(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
|
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
@@ -15,23 +14,28 @@ import WiFiNetworkSelector from './WiFiNetworkSelector';
|
|||||||
const NUM_POLLS = 10;
|
const NUM_POLLS = 10;
|
||||||
const POLLING_FREQUENCY = 1000;
|
const POLLING_FREQUENCY = 1000;
|
||||||
|
|
||||||
const WiFiNetworkScanner: FC = () => {
|
const WiFiNetworkScanner = () => {
|
||||||
const pollCount = useRef(0);
|
const pollCount = useRef(0);
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
const [errorMessage, setErrorMessage] = useState<string>();
|
||||||
|
|
||||||
const { send: scanNetworks, onComplete: onCompleteScanNetworks } = useRequest(
|
// is called on page load to start network scan
|
||||||
NetworkApi.scanNetworks
|
const { send: scanNetworks } = useRequest(NetworkApi.scanNetworks).onComplete(
|
||||||
); // is called on page load to start network scan
|
() => {
|
||||||
const {
|
pollCount.current = 0;
|
||||||
data: networkList,
|
setErrorMessage(undefined);
|
||||||
send: getNetworkList,
|
void updateState(NetworkApi.listNetworks(), () => undefined);
|
||||||
onSuccess: onSuccessNetworkList
|
void getNetworkList();
|
||||||
} = useRequest(NetworkApi.listNetworks, {
|
}
|
||||||
immediate: false
|
);
|
||||||
});
|
|
||||||
|
|
||||||
onSuccessNetworkList((event) => {
|
const { data: networkList, send: getNetworkList } = useRequest(
|
||||||
|
NetworkApi.listNetworks,
|
||||||
|
{
|
||||||
|
immediate: false
|
||||||
|
}
|
||||||
|
).onSuccess((event) => {
|
||||||
|
// is called when network scan is completed
|
||||||
if (!event.data) {
|
if (!event.data) {
|
||||||
const completedPollCount = pollCount.current + 1;
|
const completedPollCount = pollCount.current + 1;
|
||||||
if (completedPollCount < NUM_POLLS) {
|
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 = () => {
|
const renderNetworkScanner = () => {
|
||||||
if (!networkList) {
|
if (!networkList) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import LockIcon from '@mui/icons-material/Lock';
|
import LockIcon from '@mui/icons-material/Lock';
|
||||||
import LockOpenIcon from '@mui/icons-material/LockOpen';
|
import LockOpenIcon from '@mui/icons-material/LockOpen';
|
||||||
@@ -18,15 +17,11 @@ import type { Theme } from '@mui/material';
|
|||||||
|
|
||||||
import { MessageBox } from 'components';
|
import { MessageBox } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { WiFiNetwork, WiFiNetworkList } from 'types';
|
import type { WiFiNetwork } from 'types';
|
||||||
import { WiFiEncryptionType } from 'types';
|
import { WiFiEncryptionType } from 'types';
|
||||||
|
|
||||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||||
|
|
||||||
interface WiFiNetworkSelectorProps {
|
|
||||||
networkList: WiFiNetworkList;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isNetworkOpen = ({ encryption_type }: WiFiNetwork) =>
|
export const isNetworkOpen = ({ encryption_type }: WiFiNetwork) =>
|
||||||
encryption_type === WiFiEncryptionType.WIFI_AUTH_OPEN;
|
encryption_type === WiFiEncryptionType.WIFI_AUTH_OPEN;
|
||||||
|
|
||||||
@@ -62,7 +57,7 @@ const networkQualityHighlight = ({ rssi }: WiFiNetwork, theme: Theme) => {
|
|||||||
return theme.palette.success.main;
|
return theme.palette.success.main;
|
||||||
};
|
};
|
||||||
|
|
||||||
const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => {
|
function WiFiNetworkSelector({ networkList }) {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@@ -100,6 +95,6 @@ const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <List>{networkList.networks.map(renderNetwork)}</List>;
|
return <List>{networkList.networks.map(renderNetwork)}</List>;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default WiFiNetworkSelector;
|
export default WiFiNetworkSelector;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import {
|
import {
|
||||||
@@ -21,12 +20,7 @@ import { useRequest } from 'alova/client';
|
|||||||
import { MessageBox } from 'components';
|
import { MessageBox } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
interface GenerateTokenProps {
|
export default function GenerateToken({ username, onClose }) {
|
||||||
username?: string;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const open = !!username;
|
const open = !!username;
|
||||||
|
|
||||||
@@ -85,6 +79,4 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
|
|||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default GenerateToken;
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
import { useBlocker } from 'react-router-dom';
|
import { useBlocker } from 'react-router-dom';
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
@@ -40,7 +39,7 @@ import { createUserValidator } from 'validators';
|
|||||||
import GenerateToken from './GenerateToken';
|
import GenerateToken from './GenerateToken';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
|
|
||||||
const ManageUsers: FC = () => {
|
const ManageUsers = () => {
|
||||||
const { loadData, saveData, saving, data, updateDataValue, errorMessage } =
|
const { loadData, saveData, saving, data, updateDataValue, errorMessage } =
|
||||||
useRest<SecuritySettingsType>({
|
useRest<SecuritySettingsType>({
|
||||||
read: SecurityApi.readSecuritySettings,
|
read: SecurityApi.readSecuritySettings,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
import { Tab } from '@mui/material';
|
import { Tab } from '@mui/material';
|
||||||
@@ -9,7 +8,7 @@ import { useI18nContext } from 'i18n/i18n-react';
|
|||||||
import ManageUsers from './ManageUsers';
|
import ManageUsers from './ManageUsers';
|
||||||
import SecuritySettings from './SecuritySettings';
|
import SecuritySettings from './SecuritySettings';
|
||||||
|
|
||||||
const Security: FC = () => {
|
const Security = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle(LL.SETTINGS_OF(LL.SECURITY(0)));
|
useLayoutTitle(LL.SETTINGS_OF(LL.SECURITY(0)));
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
@@ -22,7 +21,7 @@ import type { SecuritySettingsType } from 'types';
|
|||||||
import { updateValueDirty, useRest } from 'utils';
|
import { updateValueDirty, useRest } from 'utils';
|
||||||
import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators';
|
import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators';
|
||||||
|
|
||||||
const SecuritySettings: FC = () => {
|
const SecuritySettings = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
|||||||
@@ -29,10 +29,8 @@ import { validate } from 'validators';
|
|||||||
interface UserFormProps {
|
interface UserFormProps {
|
||||||
creating: boolean;
|
creating: boolean;
|
||||||
validator: Schema;
|
validator: Schema;
|
||||||
|
|
||||||
user?: UserType;
|
user?: UserType;
|
||||||
setUser: React.Dispatch<React.SetStateAction<UserType | undefined>>;
|
setUser: React.Dispatch<React.SetStateAction<UserType | undefined>>;
|
||||||
|
|
||||||
onDoneEditing: () => void;
|
onDoneEditing: () => void;
|
||||||
onCancelEditing: () => void;
|
onCancelEditing: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import ComputerIcon from '@mui/icons-material/Computer';
|
import ComputerIcon from '@mui/icons-material/Computer';
|
||||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
@@ -37,7 +35,7 @@ export const apStatusHighlight = ({ status }: APStatusType, theme: Theme) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const APStatus: FC = () => {
|
const APStatus = () => {
|
||||||
const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus);
|
const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus);
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
@@ -22,7 +21,7 @@ import type { Translation } from 'i18n/i18n-types';
|
|||||||
import * as EMSESP from '../main/api';
|
import * as EMSESP from '../main/api';
|
||||||
import type { Stat } from '../main/types';
|
import type { Stat } from '../main/types';
|
||||||
|
|
||||||
const SystemActivity: FC = () => {
|
const SystemActivity = () => {
|
||||||
const { data: data, send: loadData, error } = useRequest(EMSESP.readActivity);
|
const { data: data, send: loadData, error } = useRequest(EMSESP.readActivity);
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import AppsIcon from '@mui/icons-material/Apps';
|
import AppsIcon from '@mui/icons-material/Apps';
|
||||||
import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard';
|
import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard';
|
||||||
import DevicesIcon from '@mui/icons-material/Devices';
|
import DevicesIcon from '@mui/icons-material/Devices';
|
||||||
@@ -32,7 +30,7 @@ function formatNumber(num: number) {
|
|||||||
return new Intl.NumberFormat().format(num);
|
return new Intl.NumberFormat().format(num);
|
||||||
}
|
}
|
||||||
|
|
||||||
const HardwareStatus: FC = () => {
|
const HardwareStatus = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
useLayoutTitle(LL.STATUS_OF(LL.HARDWARE()));
|
useLayoutTitle(LL.STATUS_OF(LL.HARDWARE()));
|
||||||
@@ -41,7 +39,7 @@ const HardwareStatus: FC = () => {
|
|||||||
data: data,
|
data: data,
|
||||||
send: loadData,
|
send: loadData,
|
||||||
error
|
error
|
||||||
} = useRequest(SystemApi.readHardwareStatus, { force: true });
|
} = useRequest(SystemApi.readHardwareStatus);
|
||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion';
|
import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion';
|
||||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
@@ -57,7 +55,7 @@ export const mqttQueueHighlight = (
|
|||||||
return theme.palette.warning.main;
|
return theme.palette.warning.main;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MqttStatus: FC = () => {
|
const MqttStatus = () => {
|
||||||
const { data: data, send: loadData, error } = useRequest(MqttApi.readMqttStatus);
|
const { data: data, send: loadData, error } = useRequest(MqttApi.readMqttStatus);
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||||
@@ -37,7 +36,7 @@ import type { NTPStatusType, Time } from 'types';
|
|||||||
import { NTPSyncStatus } from 'types';
|
import { NTPSyncStatus } from 'types';
|
||||||
import { formatDateTime, formatLocalDateTime } from 'utils';
|
import { formatDateTime, formatLocalDateTime } from 'utils';
|
||||||
|
|
||||||
const NTPStatus: FC = () => {
|
const NTPStatus = () => {
|
||||||
const { data: data, send: loadData, error } = useRequest(NTPApi.readNTPStatus);
|
const { data: data, send: loadData, error } = useRequest(NTPApi.readNTPStatus);
|
||||||
|
|
||||||
const [localTime, setLocalTime] = useState<string>('');
|
const [localTime, setLocalTime] = useState<string>('');
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||||
import DnsIcon from '@mui/icons-material/Dns';
|
import DnsIcon from '@mui/icons-material/Dns';
|
||||||
import GiteIcon from '@mui/icons-material/Gite';
|
import GiteIcon from '@mui/icons-material/Gite';
|
||||||
@@ -84,7 +82,7 @@ const IPs = (status: NetworkStatusType) => {
|
|||||||
return status.local_ip + ', ' + status.local_ipv6;
|
return status.local_ip + ', ' + status.local_ipv6;
|
||||||
};
|
};
|
||||||
|
|
||||||
const NetworkStatus: FC = () => {
|
const NetworkStatus = () => {
|
||||||
const {
|
const {
|
||||||
data: data,
|
data: data,
|
||||||
send: loadData,
|
send: loadData,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import * as SystemApi from 'api/system';
|
import * as SystemApi from 'api/system';
|
||||||
|
|
||||||
@@ -10,11 +9,11 @@ import { useI18nContext } from 'i18n/i18n-react';
|
|||||||
const RESTART_TIMEOUT = 2 * 60 * 1000;
|
const RESTART_TIMEOUT = 2 * 60 * 1000;
|
||||||
const POLL_INTERVAL = 3000;
|
const POLL_INTERVAL = 3000;
|
||||||
|
|
||||||
const RestartMonitor: FC = () => {
|
const RestartMonitor = () => {
|
||||||
const [failed, setFailed] = useState<boolean>(false);
|
const [failed, setFailed] = useState<boolean>(false);
|
||||||
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
|
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const { send } = useRequest(SystemApi.readSystemStatus, { force: true });
|
const { send } = useRequest(SystemApi.readSystemStatus);
|
||||||
const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT);
|
const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT);
|
||||||
|
|
||||||
const poll = useRef(async () => {
|
const poll = useRef(async () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type FC, useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ import { NTPSyncStatus, NetworkConnectionStatus } from 'types';
|
|||||||
|
|
||||||
import RestartMonitor from './RestartMonitor';
|
import RestartMonitor from './RestartMonitor';
|
||||||
|
|
||||||
const SystemStatus: FC = () => {
|
const SystemStatus = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -272,12 +272,13 @@ const SystemStatus: FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
// if (!data) {
|
// TODO remove test code
|
||||||
// return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <>fddfdd</>;
|
return <>not loaded!</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -411,7 +412,9 @@ const SystemStatus: FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return <SectionContent>{content()}</SectionContent>;
|
return (
|
||||||
|
<SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SystemStatus;
|
export default SystemStatus;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||||
@@ -71,7 +70,7 @@ const levelLabel = (level: LogLevel) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const SystemLog: FC = () => {
|
const SystemLog = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
useLayoutTitle(LL.LOG_OF(LL.SYSTEM(0)));
|
useLayoutTitle(LL.LOG_OF(LL.SYSTEM(0)));
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type ChangeEventHandler, type FC, useContext } from 'react';
|
import { type ChangeEventHandler, useContext } from 'react';
|
||||||
|
|
||||||
import { MenuItem, TextField } from '@mui/material';
|
import { MenuItem, TextField } from '@mui/material';
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ import { I18nContext } from 'i18n/i18n-react';
|
|||||||
import type { Locales } from 'i18n/i18n-types';
|
import type { Locales } from 'i18n/i18n-types';
|
||||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||||
|
|
||||||
const LanguageSelector: FC = () => {
|
const LanguageSelector = () => {
|
||||||
const { setLocale, locale } = useContext(I18nContext);
|
const { setLocale, locale } = useContext(I18nContext);
|
||||||
|
|
||||||
const onLocaleSelected: ChangeEventHandler<HTMLInputElement> = async ({
|
const onLocaleSelected: ChangeEventHandler<HTMLInputElement> = async ({
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
@@ -7,12 +6,7 @@ import { AppBar, IconButton, Toolbar, Typography } from '@mui/material';
|
|||||||
|
|
||||||
export const DRAWER_WIDTH = 210;
|
export const DRAWER_WIDTH = 210;
|
||||||
|
|
||||||
interface LayoutAppBarProps {
|
export default function LayoutAppBar({ title, onToggleDrawer }) {
|
||||||
title: string;
|
|
||||||
onToggleDrawer: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LayoutAppBar: FC<LayoutAppBarProps> = ({ title, onToggleDrawer }) => {
|
|
||||||
const pathnames = useLocation()
|
const pathnames = useLocation()
|
||||||
.pathname.split('/')
|
.pathname.split('/')
|
||||||
.filter((x) => x);
|
.filter((x) => x);
|
||||||
@@ -56,6 +50,4 @@ const LayoutAppBar: FC<LayoutAppBarProps> = ({ title, onToggleDrawer }) => {
|
|||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default LayoutAppBar;
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
|
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
|
||||||
|
|
||||||
import { PROJECT_NAME } from 'api/env';
|
import { PROJECT_NAME } from 'api/env';
|
||||||
@@ -18,12 +16,7 @@ const LayoutDrawerLogo = styled('img')(({ theme }) => ({
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface LayoutDrawerProps {
|
export default function LayoutDrawerProps({ mobileOpen, onClose }) {
|
||||||
mobileOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LayoutDrawer: FC<LayoutDrawerProps> = ({ mobileOpen, onClose }) => {
|
|
||||||
const drawer = (
|
const drawer = (
|
||||||
<>
|
<>
|
||||||
<Toolbar disableGutters>
|
<Toolbar disableGutters>
|
||||||
@@ -66,6 +59,4 @@ const LayoutDrawer: FC<LayoutDrawerProps> = ({ mobileOpen, onClose }) => {
|
|||||||
</Drawer>
|
</Drawer>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default LayoutDrawer;
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
||||||
import AssessmentIcon from '@mui/icons-material/Assessment';
|
import AssessmentIcon from '@mui/icons-material/Assessment';
|
||||||
@@ -30,7 +29,7 @@ import LayoutMenuItem from 'components/layout/LayoutMenuItem';
|
|||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
const LayoutMenu: FC = () => {
|
const LayoutMenu = () => {
|
||||||
const { me, signOut } = useContext(AuthenticatedContext);
|
const { me, signOut } = useContext(AuthenticatedContext);
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
|
||||||
import { Box, Paper, Typography } from '@mui/material';
|
|
||||||
|
|
||||||
interface ApplicationErrorProps {
|
|
||||||
message?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ApplicationError: FC<ApplicationErrorProps> = ({ message }) => (
|
|
||||||
<Box display="flex" height="100vh" justifyContent="center" flexDirection="column">
|
|
||||||
<Paper
|
|
||||||
elevation={10}
|
|
||||||
sx={{
|
|
||||||
textAlign: 'center',
|
|
||||||
padding: '280px 0 40px 0',
|
|
||||||
backgroundImage: 'url("/app/icon.png")',
|
|
||||||
backgroundRepeat: 'no-repeat',
|
|
||||||
backgroundPosition: '50% 40px',
|
|
||||||
backgroundSize: '200px auto',
|
|
||||||
width: '100%',
|
|
||||||
borderRadius: 0
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
display="flex"
|
|
||||||
flexDirection="row"
|
|
||||||
justifyContent="center"
|
|
||||||
alignItems="center"
|
|
||||||
mb={2}
|
|
||||||
>
|
|
||||||
<WarningIcon fontSize="large" color="error" />
|
|
||||||
<Box ml={2}>
|
|
||||||
<Typography variant="h4">Application Error</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Typography variant="subtitle1" gutterBottom>
|
|
||||||
Failed to configure the application, please refresh to try again.
|
|
||||||
</Typography>
|
|
||||||
{message && (
|
|
||||||
<Typography variant="subtitle2" gutterBottom>
|
|
||||||
{message}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default ApplicationError;
|
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
export { default as ApplicationError } from './ApplicationError';
|
|
||||||
export { default as LoadingSpinner } from './LoadingSpinner';
|
export { default as LoadingSpinner } from './LoadingSpinner';
|
||||||
export { default as FormLoader } from './FormLoader';
|
export { default as FormLoader } from './FormLoader';
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
import type { Blocker } from 'react-router-dom';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -12,11 +9,7 @@ import {
|
|||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
interface BlockNavigationProps {
|
export default function BlockNavigation({ blocker }) {
|
||||||
blocker: Blocker;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BlockNavigation: FC<BlockNavigationProps> = ({ blocker }) => {
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -41,6 +34,4 @@ const BlockNavigation: FC<BlockNavigationProps> = ({ blocker }) => {
|
|||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default BlockNavigation;
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
|||||||
import { Box, Button, LinearProgress, Typography, useTheme } from '@mui/material';
|
import { Box, Button, LinearProgress, Typography, useTheme } from '@mui/material';
|
||||||
import type { Theme } from '@mui/material';
|
import type { Theme } from '@mui/material';
|
||||||
|
|
||||||
import type { Progress } from 'alova/client';
|
import type { Progress } from 'alova';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
const getBorderColor = (theme: Theme, props: DropzoneState) => {
|
const getBorderColor = (theme: Theme, props: DropzoneState) => {
|
||||||
|
|||||||
@@ -252,9 +252,9 @@ const en: Translation = {
|
|||||||
TIME_ZONE: 'Time Zone',
|
TIME_ZONE: 'Time Zone',
|
||||||
ACCESS_POINT: 'Access Point',
|
ACCESS_POINT: 'Access Point',
|
||||||
AP_PROVIDE: 'Enable Access Point',
|
AP_PROVIDE: 'Enable Access Point',
|
||||||
AP_PROVIDE_TEXT_1: 'always',
|
AP_PROVIDE_TEXT_1: 'Always',
|
||||||
AP_PROVIDE_TEXT_2: 'when WiFi is disconnected',
|
AP_PROVIDE_TEXT_2: 'When WiFi is disconnected',
|
||||||
AP_PROVIDE_TEXT_3: 'never',
|
AP_PROVIDE_TEXT_3: 'Never',
|
||||||
AP_PREFERRED_CHANNEL: 'Preferred Channel',
|
AP_PREFERRED_CHANNEL: 'Preferred Channel',
|
||||||
AP_HIDE_SSID: 'Hide SSID',
|
AP_HIDE_SSID: 'Hide SSID',
|
||||||
AP_CLIENTS: 'AP Clients',
|
AP_CLIENTS: 'AP Clients',
|
||||||
|
|||||||
@@ -24,30 +24,22 @@ export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
|
|||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
send: readData,
|
send: readData,
|
||||||
update: updateData,
|
update: updateData
|
||||||
// TODO refactor
|
} = useRequest(read()).onComplete((event) => {
|
||||||
onComplete: onReadComplete
|
setOrigData(event.data as D);
|
||||||
} = useRequest(read());
|
});
|
||||||
|
|
||||||
const {
|
const { loading: saving, send: writeData } = useRequest(
|
||||||
loading: saving,
|
(newData: D) => update(newData),
|
||||||
send: writeData,
|
{ immediate: false }
|
||||||
// TODO refactor
|
).onSuccess(() => {
|
||||||
onSuccess: onWriteSuccess
|
|
||||||
} = useRequest((newData: D) => update(newData), { immediate: false });
|
|
||||||
|
|
||||||
const updateDataValue = (new_data: D) => {
|
|
||||||
updateData({ data: new_data });
|
|
||||||
};
|
|
||||||
|
|
||||||
onWriteSuccess(() => {
|
|
||||||
toast.success(LL.UPDATED_OF(LL.SETTINGS(1)));
|
toast.success(LL.UPDATED_OF(LL.SETTINGS(1)));
|
||||||
setDirtyFlags([]);
|
setDirtyFlags([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
onReadComplete((event) => {
|
const updateDataValue = (new_data: D) => {
|
||||||
setOrigData(event.data as D);
|
updateData({ data: new_data });
|
||||||
});
|
};
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
setDirtyFlags([]);
|
setDirtyFlags([]);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const validate = <T extends object>(
|
|||||||
options ? options : {},
|
options ? options : {},
|
||||||
(errors, fieldErrors) => {
|
(errors, fieldErrors) => {
|
||||||
if (errors) {
|
if (errors) {
|
||||||
reject(fieldErrors);
|
reject(fieldErrors as Error);
|
||||||
} else {
|
} else {
|
||||||
resolve(source as T);
|
resolve(source as T);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1705,13 +1705,13 @@ __metadata:
|
|||||||
react-router-dom: "npm:^6.26.0"
|
react-router-dom: "npm:^6.26.0"
|
||||||
react-toastify: "npm:^10.0.5"
|
react-toastify: "npm:^10.0.5"
|
||||||
rollup-plugin-visualizer: "npm:^5.12.0"
|
rollup-plugin-visualizer: "npm:^5.12.0"
|
||||||
terser: "npm:^5.31.3"
|
terser: "npm:^5.31.5"
|
||||||
typesafe-i18n: "npm:^5.26.2"
|
typesafe-i18n: "npm:^5.26.2"
|
||||||
typescript: "npm:^5.5.4"
|
typescript: "npm:^5.5.4"
|
||||||
typescript-eslint: "npm:8.0.1"
|
typescript-eslint: "npm:8.0.1"
|
||||||
vite: "npm:^5.3.5"
|
vite: "npm:^5.4.0"
|
||||||
vite-plugin-imagemin: "npm:^0.6.1"
|
vite-plugin-imagemin: "npm:^0.6.1"
|
||||||
vite-tsconfig-paths: "npm:^4.3.2"
|
vite-tsconfig-paths: "npm:^5.0.0"
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
@@ -5611,14 +5611,14 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"postcss@npm:^8.4.39":
|
"postcss@npm:^8.4.40":
|
||||||
version: 8.4.40
|
version: 8.4.41
|
||||||
resolution: "postcss@npm:8.4.40"
|
resolution: "postcss@npm:8.4.41"
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: "npm:^3.3.7"
|
nanoid: "npm:^3.3.7"
|
||||||
picocolors: "npm:^1.0.1"
|
picocolors: "npm:^1.0.1"
|
||||||
source-map-js: "npm:^1.2.0"
|
source-map-js: "npm:^1.2.0"
|
||||||
checksum: 10c0/65ed67573e5443beaeb582282ff27a6be7c7fe3b4d9fa15761157616f2b97510cb1c335023c26220b005909f007337026d6e3ff092f25010b484ad484e80ea7f
|
checksum: 10c0/c1828fc59e7ec1a3bf52b3a42f615dba53c67960ed82a81df6441b485fe43c20aba7f4e7c55425762fd99c594ecabbaaba8cf5b30fd79dfec5b52a9f63a2d690
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -6686,9 +6686,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"terser@npm:^5.31.3":
|
"terser@npm:^5.31.5":
|
||||||
version: 5.31.3
|
version: 5.31.5
|
||||||
resolution: "terser@npm:5.31.3"
|
resolution: "terser@npm:5.31.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@jridgewell/source-map": "npm:^0.3.3"
|
"@jridgewell/source-map": "npm:^0.3.3"
|
||||||
acorn: "npm:^8.8.2"
|
acorn: "npm:^8.8.2"
|
||||||
@@ -6696,7 +6696,7 @@ __metadata:
|
|||||||
source-map-support: "npm:~0.5.20"
|
source-map-support: "npm:~0.5.20"
|
||||||
bin:
|
bin:
|
||||||
terser: bin/terser
|
terser: bin/terser
|
||||||
checksum: 10c0/eb2b525dada9febd3db74e94bd295f9cd7abd809e4f9c6bbc795a3048ad50fd327c15eab99db383fa820239680eef6d2dbd7dc05361769c204ddee5cf684d41e
|
checksum: 10c0/6e7c66c1f4062ee098bff3dc3c396819ebf5f1740f0615be9de39b675a78c732d199f4dcfdcd15bd65f354e37c45bb944360f532a36fe7f7d22f800ca53c2d02
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -7025,9 +7025,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"vite-tsconfig-paths@npm:^4.3.2":
|
"vite-tsconfig-paths@npm:^5.0.0":
|
||||||
version: 4.3.2
|
version: 5.0.0
|
||||||
resolution: "vite-tsconfig-paths@npm:4.3.2"
|
resolution: "vite-tsconfig-paths@npm:5.0.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: "npm:^4.1.1"
|
debug: "npm:^4.1.1"
|
||||||
globrex: "npm:^0.1.2"
|
globrex: "npm:^0.1.2"
|
||||||
@@ -7037,23 +7037,24 @@ __metadata:
|
|||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
vite:
|
vite:
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 10c0/f390ac1d1c3992fc5ac50f9274c1090f8b55ab34a89ea88893db9a6924a3b26c9f64bc1163615150ad100749db73b6b2cf1d57f6cd60df6e762ceb5b8ad30024
|
checksum: 10c0/c29284abb92e829558f4913fb4e6e8ee2581e90c0ad13d6c88f4c16250d03cde2d14729af63df76d801babfc3ee9c6ccff5a421142d7af4f497beab988e9196c
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"vite@npm:^5.3.5":
|
"vite@npm:^5.4.0":
|
||||||
version: 5.3.5
|
version: 5.4.0
|
||||||
resolution: "vite@npm:5.3.5"
|
resolution: "vite@npm:5.4.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: "npm:^0.21.3"
|
esbuild: "npm:^0.21.3"
|
||||||
fsevents: "npm:~2.3.3"
|
fsevents: "npm:~2.3.3"
|
||||||
postcss: "npm:^8.4.39"
|
postcss: "npm:^8.4.40"
|
||||||
rollup: "npm:^4.13.0"
|
rollup: "npm:^4.13.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@types/node": ^18.0.0 || >=20.0.0
|
"@types/node": ^18.0.0 || >=20.0.0
|
||||||
less: "*"
|
less: "*"
|
||||||
lightningcss: ^1.21.0
|
lightningcss: ^1.21.0
|
||||||
sass: "*"
|
sass: "*"
|
||||||
|
sass-embedded: "*"
|
||||||
stylus: "*"
|
stylus: "*"
|
||||||
sugarss: "*"
|
sugarss: "*"
|
||||||
terser: ^5.4.0
|
terser: ^5.4.0
|
||||||
@@ -7069,6 +7070,8 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
sass:
|
sass:
|
||||||
optional: true
|
optional: true
|
||||||
|
sass-embedded:
|
||||||
|
optional: true
|
||||||
stylus:
|
stylus:
|
||||||
optional: true
|
optional: true
|
||||||
sugarss:
|
sugarss:
|
||||||
@@ -7077,7 +7080,7 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
bin:
|
bin:
|
||||||
vite: bin/vite.js
|
vite: bin/vite.js
|
||||||
checksum: 10c0/795c7e0dbc94b96c4a0aff0d5d4b349dd28ad8b7b70979c1010f96b4d83f7d6c1700ebd6fed91de2e021b0a3689b9abc2d8017f6dfa8c9a6ca5c7af637d6afc6
|
checksum: 10c0/122de7795e1c3c08cd0acc7d77296f908398266b424492be7310400107f37a3cf4c9506f2b4b16619e57299ca2859b8ca187aac5e25f8e66d84f9204a1d72d18
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
@@ -4356,7 +4356,7 @@ router
|
|||||||
)
|
)
|
||||||
.get(EMSESP_DEVICEENTITIES_ENDPOINT2, ({ params }) => (params.id ? deviceEntities(Number(params.id)) : status(404)))
|
.get(EMSESP_DEVICEENTITIES_ENDPOINT2, ({ params }) => (params.id ? deviceEntities(Number(params.id)) : status(404)))
|
||||||
|
|
||||||
// Customization
|
// Customizations
|
||||||
.post(EMSESP_CUSTOMIZATION_ENTITIES_ENDPOINT, async (request: any) => {
|
.post(EMSESP_CUSTOMIZATION_ENTITIES_ENDPOINT, async (request: any) => {
|
||||||
const content = await request.json();
|
const content = await request.json();
|
||||||
const id = content.id;
|
const id = content.id;
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ void WebCustomizationService::device_entities(AsyncWebServerRequest * request) {
|
|||||||
#endif
|
#endif
|
||||||
#if defined(EMSESP_DEBUG)
|
#if defined(EMSESP_DEBUG)
|
||||||
size_t length = response->setLength();
|
size_t length = response->setLength();
|
||||||
EMSESP::logger().debug("Customization buffer used: %d", length);
|
EMSESP::logger().debug("Customizations buffer used: %d", length);
|
||||||
#else
|
#else
|
||||||
response->setLength();
|
response->setLength();
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user