From 3481a879c26d61bd0ceba829b82a4d39f048f34b Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 8 Aug 2024 12:39:48 +0200 Subject: [PATCH] alova 3 --- interface/eslint.config.js | 1 + interface/package.json | 8 +- interface/src/App.tsx | 3 +- interface/src/AppRouting.tsx | 2 +- interface/src/AuthenticatedRouting.tsx | 8 +- interface/src/SignIn.tsx | 9 +- interface/src/api/network.ts | 5 +- interface/src/api/ntp.ts | 5 +- interface/src/app/main/CustomEntities.tsx | 14 +-- .../src/app/main/CustomEntitiesDialog.tsx | 6 +- .../{Customization.tsx => Customizations.tsx} | 19 ++- ...ionDialog.tsx => CustomizationsDialog.tsx} | 8 +- interface/src/app/main/DeviceIcon.tsx | 11 +- interface/src/app/main/Devices.tsx | 3 +- interface/src/app/main/Help.tsx | 23 ++-- interface/src/app/main/Modules.tsx | 35 ++---- interface/src/app/main/OptionIcon.tsx | 13 +- interface/src/app/main/Scheduler.tsx | 18 ++- interface/src/app/main/SchedulerDialog.tsx | 14 +-- interface/src/app/main/Sensors.tsx | 3 +- interface/src/app/main/api.ts | 3 - interface/src/app/settings/APSettings.tsx | 3 +- .../src/app/settings/ApplicationSettings.tsx | 28 ++--- interface/src/app/settings/MqttSettings.tsx | 3 +- interface/src/app/settings/NTPSettings.tsx | 8 +- interface/src/app/settings/Settings.tsx | 4 +- interface/src/app/settings/UploadDownload.tsx | 115 +++++++----------- .../src/app/settings/network/Network.tsx | 3 +- .../app/settings/network/NetworkSettings.tsx | 32 ++--- .../settings/network/WiFiNetworkScanner.tsx | 38 +++--- .../settings/network/WiFiNetworkSelector.tsx | 11 +- .../app/settings/security/GenerateToken.tsx | 12 +- .../src/app/settings/security/ManageUsers.tsx | 3 +- .../src/app/settings/security/Security.tsx | 3 +- .../settings/security/SecuritySettings.tsx | 3 +- interface/src/app/settings/security/User.tsx | 2 - interface/src/app/status/APStatus.tsx | 4 +- interface/src/app/status/Activity.tsx | 3 +- interface/src/app/status/HardwareStatus.tsx | 6 +- interface/src/app/status/MqttStatus.tsx | 4 +- interface/src/app/status/NTPStatus.tsx | 3 +- interface/src/app/status/NetworkStatus.tsx | 4 +- interface/src/app/status/RestartMonitor.tsx | 5 +- interface/src/app/status/Status.tsx | 19 +-- interface/src/app/status/SystemLog.tsx | 3 +- .../components/inputs/LanguageSelector.tsx | 4 +- .../src/components/layout/LayoutAppBar.tsx | 12 +- .../src/components/layout/LayoutDrawer.tsx | 13 +- .../src/components/layout/LayoutMenu.tsx | 3 +- .../components/loading/ApplicationError.tsx | 49 -------- interface/src/components/loading/index.ts | 1 - .../components/routing/BlockNavigation.tsx | 13 +- .../src/components/upload/SingleUpload.tsx | 2 +- interface/src/i18n/en/index.ts | 6 +- interface/src/utils/useRest.ts | 30 ++--- interface/src/validators/shared.ts | 2 +- interface/yarn.lock | 43 ++++--- mock-api/rest_server.ts | 2 +- src/web/WebCustomizationService.cpp | 2 +- 59 files changed, 259 insertions(+), 453 deletions(-) rename interface/src/app/main/{Customization.tsx => Customizations.tsx} (98%) rename interface/src/app/main/{CustomizationDialog.tsx => CustomizationsDialog.tsx} (96%) delete mode 100644 interface/src/components/loading/ApplicationError.tsx diff --git a/interface/eslint.config.js b/interface/eslint.config.js index 9a3d014d9..c6d578b18 100644 --- a/interface/eslint.config.js +++ b/interface/eslint.config.js @@ -21,6 +21,7 @@ export default tseslint.config( { rules: { '@typescript-eslint/no-unsafe-enum-comparison': 'off', + '@typescript-eslint/no-unused-expressions': 'off', '@typescript-eslint/no-unsafe-assignment': 'off', '@typescript-eslint/no-misused-promises': [ 'error', diff --git a/interface/package.json b/interface/package.json index 4973178e5..6543125bf 100644 --- a/interface/package.json +++ b/interface/package.json @@ -33,6 +33,7 @@ "jwt-decode": "^4.0.0", "lodash-es": "^4.17.21", "mime-types": "^2.1.35", + "preact": "^10.23.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-dropzone": "^14.2.3", @@ -57,14 +58,13 @@ "concurrently": "^8.2.2", "eslint": "^9.8.0", "eslint-config-prettier": "^9.1.0", - "preact": "^10.23.1", "prettier": "^3.3.3", "rollup-plugin-visualizer": "^5.12.0", - "terser": "^5.31.3", + "terser": "^5.31.5", "typescript-eslint": "8.0.1", - "vite": "^5.3.5", + "vite": "^5.4.0", "vite-plugin-imagemin": "^0.6.1", - "vite-tsconfig-paths": "^4.3.2" + "vite-tsconfig-paths": "^5.0.0" }, "packageManager": "yarn@4.2.1" } diff --git a/interface/src/App.tsx b/interface/src/App.tsx index b5ac62222..f6c9d49be 100644 --- a/interface/src/App.tsx +++ b/interface/src/App.tsx @@ -1,5 +1,4 @@ import { useEffect, useState } from 'react'; -import type { FC } from 'react'; import { Slide, ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.min.css'; @@ -12,7 +11,7 @@ import { localStorageDetector } from 'typesafe-i18n/detectors'; const detectedLocale = detectLocale(localStorageDetector); -const App: FC = () => { +const App = () => { const [wasLoaded, setWasLoaded] = useState(false); useEffect(() => { diff --git a/interface/src/AppRouting.tsx b/interface/src/AppRouting.tsx index e54f8023f..df857d07e 100644 --- a/interface/src/AppRouting.tsx +++ b/interface/src/AppRouting.tsx @@ -37,7 +37,7 @@ export const RemoveTrailingSlashes = () => { ); }; -const AppRouting: FC = () => { +const AppRouting = () => { const { LL } = useI18nContext(); return ( diff --git a/interface/src/AuthenticatedRouting.tsx b/interface/src/AuthenticatedRouting.tsx index cdd00b09e..0a7c283b1 100644 --- a/interface/src/AuthenticatedRouting.tsx +++ b/interface/src/AuthenticatedRouting.tsx @@ -1,8 +1,8 @@ -import { type FC, useContext } from 'react'; +import { useContext } from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; 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 Help from 'app/main/Help'; import Modules from 'app/main/Modules'; @@ -27,7 +27,7 @@ import SystemLog from 'app/status/SystemLog'; import { Layout } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; -const AuthenticatedRouting: FC = () => { +const AuthenticatedRouting = () => { const { me } = useContext(AuthenticatedContext); return ( @@ -59,7 +59,7 @@ const AuthenticatedRouting: FC = () => { } /> } /> - } /> + } /> } /> } /> diff --git a/interface/src/SignIn.tsx b/interface/src/SignIn.tsx index 36a533ba4..e21cd8874 100644 --- a/interface/src/SignIn.tsx +++ b/interface/src/SignIn.tsx @@ -1,5 +1,4 @@ import { useContext, useState } from 'react'; -import type { FC } from 'react'; import { toast } from 'react-toastify'; import ForwardIcon from '@mui/icons-material/Forward'; @@ -21,7 +20,7 @@ import type { SignInRequest } from 'types'; import { onEnterCallback, updateValue } from 'utils'; import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators'; -const SignIn: FC = () => { +const SignIn = () => { const authenticationContext = useContext(AuthenticationContext); const { LL } = useI18nContext(); @@ -33,14 +32,12 @@ const SignIn: FC = () => { const [processing, setProcessing] = useState(false); const [fieldErrors, setFieldErrors] = useState(); - const { send: callSignIn, onSuccess } = useRequest( + const { send: callSignIn } = useRequest( (request: SignInRequest) => AuthenticationApi.signIn(request), { immediate: false } - ); - - onSuccess((response) => { + ).onSuccess((response) => { if (response.data) { authenticationContext.signIn(response.data.access_token); } diff --git a/interface/src/api/network.ts b/interface/src/api/network.ts index ebbec684a..12222129c 100644 --- a/interface/src/api/network.ts +++ b/interface/src/api/network.ts @@ -7,12 +7,9 @@ export const readNetworkStatus = () => export const scanNetworks = () => alovaInstance.Get('/rest/scanNetworks'); export const listNetworks = () => alovaInstance.Get('/rest/listNetworks', { - name: 'listNetworks', timeout: 20000 // timeout 20 seconds }); export const readNetworkSettings = () => - alovaInstance.Get('/rest/networkSettings', { - name: 'networkSettings' - }); + alovaInstance.Get('/rest/networkSettings'); export const updateNetworkSettings = (wifiSettings: NetworkSettingsType) => alovaInstance.Post('/rest/networkSettings', wifiSettings); diff --git a/interface/src/api/ntp.ts b/interface/src/api/ntp.ts index 69e7471b4..443d078b1 100644 --- a/interface/src/api/ntp.ts +++ b/interface/src/api/ntp.ts @@ -4,10 +4,9 @@ import { alovaInstance } from './endpoints'; export const readNTPStatus = () => alovaInstance.Get('/rest/ntpStatus'); + export const readNTPSettings = () => - alovaInstance.Get('/rest/ntpSettings', { - name: 'ntpSettings' - }); + alovaInstance.Get('/rest/ntpSettings', {}); export const updateNTPSettings = (data: NTPSettingsType) => alovaInstance.Post('/rest/ntpSettings', data); diff --git a/interface/src/app/main/CustomEntities.tsx b/interface/src/app/main/CustomEntities.tsx index ebf776a68..38c7b9c47 100644 --- a/interface/src/app/main/CustomEntities.tsx +++ b/interface/src/app/main/CustomEntities.tsx @@ -1,5 +1,4 @@ import { useCallback, useState } from 'react'; -import type { FC } from 'react'; import { useBlocker } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -30,13 +29,13 @@ import { } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import * as EMSESP from './api'; import SettingsCustomEntitiesDialog from './CustomEntitiesDialog'; +import { readCustomEntities, writeCustomEntities } from './api'; import { DeviceValueTypeNames, DeviceValueUOM_s } from './types'; import type { Entities, EntityItem } from './types'; import { entityItemValidation } from './validators'; -const CustomEntities: FC = () => { +const CustomEntities = () => { const { LL } = useI18nContext(); const [numChanges, setNumChanges] = useState(0); const blocker = useBlocker(numChanges !== 0); @@ -50,13 +49,12 @@ const CustomEntities: FC = () => { data: entities, send: fetchEntities, error - } = useRequest(EMSESP.readCustomEntities, { - initialData: [], - force: true + } = useRequest(readCustomEntities, { + initialData: [] }); const { send: writeEntities } = useRequest( - (data: Entities) => EMSESP.writeCustomEntities(data), + (data: Entities) => writeCustomEntities(data), { immediate: false } ); @@ -182,7 +180,7 @@ const CustomEntities: FC = () => { const onDialogSave = (updatedItem: EntityItem) => { setDialogOpen(false); - updateState('entities', (data: EntityItem[]) => { + void updateState(readCustomEntities(), (data: EntityItem[]) => { const new_data = creating ? [ ...data.filter((ei) => creating || ei.o_id !== updatedItem.o_id), diff --git a/interface/src/app/main/CustomEntitiesDialog.tsx b/interface/src/app/main/CustomEntitiesDialog.tsx index 79455de57..f0fe08fe3 100644 --- a/interface/src/app/main/CustomEntitiesDialog.tsx +++ b/interface/src/app/main/CustomEntitiesDialog.tsx @@ -291,7 +291,11 @@ const CustomEntitiesDialog = ({ fullWidth margin="normal" type="number" - inputProps={{ min: '1', max: String(256 - editItem.offset), step: '1' }} + inputProps={{ + min: '1', + max: String(256 - editItem.offset), + step: '1' + }} /> )} diff --git a/interface/src/app/main/Customization.tsx b/interface/src/app/main/Customizations.tsx similarity index 98% rename from interface/src/app/main/Customization.tsx rename to interface/src/app/main/Customizations.tsx index b9d9311ac..269302270 100644 --- a/interface/src/app/main/Customization.tsx +++ b/interface/src/app/main/Customizations.tsx @@ -1,5 +1,4 @@ import { useCallback, useEffect, useState } from 'react'; -import type { FC } from 'react'; import { useBlocker, useLocation } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -52,7 +51,7 @@ import { import { useI18nContext } from 'i18n/i18n-react'; import * as EMSESP from './api'; -import SettingsCustomizationDialog from './CustomizationDialog'; +import SettingsCustomizationsDialog from './CustomizationsDialog'; import EntityMaskToggle from './EntityMaskToggle'; import OptionIcon from './OptionIcon'; import { DeviceEntityMask } from './types'; @@ -60,7 +59,7 @@ import type { DeviceEntity, DeviceShort } from './types'; export const APIURL = window.location.origin + '/api/'; -const Customization: FC = () => { +const Customizations = () => { const { LL } = useI18nContext(); const [numChanges, setNumChanges] = useState(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), { initialData: [], immediate: false } - ); + ).onSuccess((event) => { + setOriginalSettings(event.data); + }); const setOriginalSettings = (data: DeviceEntity[]) => { setDeviceEntities( @@ -126,10 +127,6 @@ const Customization: FC = () => { ); }; - onSuccess((event) => { - setOriginalSettings(event.data); - }); - const { send: restartCommand } = useRequest(SystemApi.restart(), { immediate: false }); @@ -727,7 +724,7 @@ const Customization: FC = () => { {blocker ? : null} {restarting ? : renderContent()} {selectedDeviceEntity && ( - { ); }; -export default Customization; +export default Customizations; diff --git a/interface/src/app/main/CustomizationDialog.tsx b/interface/src/app/main/CustomizationsDialog.tsx similarity index 96% rename from interface/src/app/main/CustomizationDialog.tsx rename to interface/src/app/main/CustomizationsDialog.tsx index cb03bcbae..b29130dba 100644 --- a/interface/src/app/main/CustomizationDialog.tsx +++ b/interface/src/app/main/CustomizationsDialog.tsx @@ -23,19 +23,19 @@ import EntityMaskToggle from './EntityMaskToggle'; import { DeviceEntityMask } from './types'; import type { DeviceEntity } from './types'; -interface SettingsCustomizationDialogProps { +interface SettingsCustomizationsDialogProps { open: boolean; onClose: () => void; onSave: (di: DeviceEntity) => void; selectedItem: DeviceEntity; } -const CustomizationDialog = ({ +const CustomizationsDialog = ({ open, onClose, onSave, selectedItem -}: SettingsCustomizationDialogProps) => { +}: SettingsCustomizationsDialogProps) => { const { LL } = useI18nContext(); const [editItem, setEditItem] = useState(selectedItem); const [error, setError] = useState(false); @@ -175,4 +175,4 @@ const CustomizationDialog = ({ ); }; -export default CustomizationDialog; +export default CustomizationsDialog; diff --git a/interface/src/app/main/DeviceIcon.tsx b/interface/src/app/main/DeviceIcon.tsx index 968d5ec6f..b4813ff45 100644 --- a/interface/src/app/main/DeviceIcon.tsx +++ b/interface/src/app/main/DeviceIcon.tsx @@ -1,4 +1,3 @@ -import type { FC } from 'react'; import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/ai'; import { CgSmartHomeBoiler } from 'react-icons/cg'; import { FaSolarPanel } from 'react-icons/fa'; @@ -16,11 +15,7 @@ import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd'; import { DeviceType } from './types'; -interface DeviceIconProps { - type_id: number; -} - -const DeviceIcon: FC = ({ type_id }) => { +export default function DeviceIcon({ type_id }) { switch (type_id as DeviceType) { case DeviceType.TEMPERATURESENSOR: case DeviceType.ANALOGSENSOR: @@ -60,6 +55,4 @@ const DeviceIcon: FC = ({ type_id }) => { default: return null; } -}; - -export default DeviceIcon; +} diff --git a/interface/src/app/main/Devices.tsx b/interface/src/app/main/Devices.tsx index d4439fbfc..d7faf8598 100644 --- a/interface/src/app/main/Devices.tsx +++ b/interface/src/app/main/Devices.tsx @@ -5,7 +5,6 @@ import { useLayoutEffect, useState } from 'react'; -import type { FC } from 'react'; import { IconContext } from 'react-icons'; import { useNavigate } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -70,7 +69,7 @@ import { DeviceEntityMask, DeviceType, DeviceValueUOM_s } from './types'; import type { Device, DeviceValue } from './types'; import { deviceValueItemValidation } from './validators'; -const Devices: FC = () => { +const Devices = () => { const { LL } = useI18nContext(); const { me } = useContext(AuthenticatedContext); diff --git a/interface/src/app/main/Help.tsx b/interface/src/app/main/Help.tsx index fa3fda51b..697026a68 100644 --- a/interface/src/app/main/Help.tsx +++ b/interface/src/app/main/Help.tsx @@ -1,4 +1,3 @@ -import type { FC } from 'react'; import { toast } from 'react-toastify'; import CommentIcon from '@mui/icons-material/CommentTwoTone'; @@ -21,27 +20,19 @@ import { import * as SystemApi from 'api/system'; import * as EMSESP from 'app/main/api'; -import { useAutoRequest, useRequest } from 'alova/client'; +import { useRequest } from 'alova/client'; import { SectionContent, useLayoutTitle } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import type { APIcall } from './types'; -const Help: FC = () => { +const Help = () => { const { LL } = useI18nContext(); useLayoutTitle(LL.HELP_OF('')); - const { send: getAPI, onSuccess: onGetAPI } = useRequest( - (data: APIcall) => EMSESP.API(data), - { - immediate: false - } - ); - - // TODO check useAutoRequest - https://alova.js.org/tutorial/client/strategy/use-auto-request/#basic-usage - const { data, loading } = useAutoRequest(SystemApi.readSystemStatus); - - onGetAPI((event) => { + const { send: getAPI } = useRequest((data: APIcall) => EMSESP.API(data), { + immediate: false + }).onSuccess((event) => { const anchor = document.createElement('a'); anchor.href = URL.createObjectURL( new Blob([JSON.stringify(event.data, null, 2)], { @@ -56,14 +47,16 @@ const Help: FC = () => { toast.info(LL.DOWNLOAD_SUCCESSFUL()); }); + const { data, loading } = useRequest(SystemApi.readSystemStatus); + const callAPI = async (device: string, entity: string) => { await getAPI({ device, entity, id: 0 }).catch((error: Error) => { toast.error(error.message); }); }; + // TODO remove debug testing useRequest preact hook console.log('loading: ' + loading + ' data2: ' + data); - if (loading) { return
Loading...
; } diff --git a/interface/src/app/main/Modules.tsx b/interface/src/app/main/Modules.tsx index 1c7313192..81bb050bf 100644 --- a/interface/src/app/main/Modules.tsx +++ b/interface/src/app/main/Modules.tsx @@ -1,5 +1,4 @@ import { useCallback, useState } from 'react'; -import type { FC } from 'react'; import { useBlocker } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -8,8 +7,6 @@ import CircleIcon from '@mui/icons-material/Circle'; import WarningIcon from '@mui/icons-material/Warning'; import { Box, Button, Typography } from '@mui/material'; -import { alovaInstance } from 'api/endpoints'; - import { Body, Cell, @@ -30,11 +27,11 @@ import { } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import * as EMSESP from './api'; import ModulesDialog from './ModulesDialog'; +import { readModules, writeModules } from './api'; import type { ModuleItem, Modules } from './types'; -const Modules: FC = () => { +const Modules = () => { const { LL } = useI18nContext(); const [numChanges, setNumChanges] = useState(0); const blocker = useBlocker(numChanges !== 0); @@ -46,13 +43,12 @@ const Modules: FC = () => { data: modules, send: fetchModules, error - } = useRequest(EMSESP.readModules, { + } = useRequest(readModules, { initialData: [] }); - const { send: writeModules } = useRequest( - (data: { key: string; enabled: boolean; license: string }) => - EMSESP.writeModules(data), + const { send: updateModules } = useRequest( + (data: { key: string; enabled: boolean; license: string }) => writeModules(data), { immediate: false } @@ -124,23 +120,18 @@ const Modules: FC = () => { 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) => { - updateState( - [alovaInstance.snapshots.match('modules', true)] as any, - (data: ModuleItem[]) => { - const new_data = data.map((mi) => - mi.id === updatedItem.id ? { ...mi, ...updatedItem } : mi - ); - setNumChanges(new_data.filter((mi) => hasModulesChanged(mi)).length); - return new_data; - } - ); + void updateState(readModules(), (data: ModuleItem[]) => { + const new_data = data.map((mi) => + mi.id === updatedItem.id ? { ...mi, ...updatedItem } : mi + ); + setNumChanges(new_data.filter((mi) => hasModulesChanged(mi)).length); + return new_data; + }); }; const saveModules = async () => { - await writeModules({ + await updateModules({ modules: modules.map((condensed_mi) => ({ key: condensed_mi.key, enabled: condensed_mi.enabled, diff --git a/interface/src/app/main/OptionIcon.tsx b/interface/src/app/main/OptionIcon.tsx index c1d7b241d..b40e96d78 100644 --- a/interface/src/app/main/OptionIcon.tsx +++ b/interface/src/app/main/OptionIcon.tsx @@ -1,5 +1,3 @@ -import type { FC } from 'react'; - import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined'; import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; @@ -32,18 +30,11 @@ const OPTION_ICONS: { favorite: [StarIcon, StarOutlineIcon] }; -interface OptionIconProps { - type: OptionType; - isSet: boolean; -} - -const OptionIcon: FC = ({ type, isSet }) => { +export default function OptionIcon({ type, isSet }) { const Icon = OPTION_ICONS[type][isSet ? 0 : 1]; return isSet ? ( ) : ( ); -}; - -export default OptionIcon; +} diff --git a/interface/src/app/main/Scheduler.tsx b/interface/src/app/main/Scheduler.tsx index 034357375..4b798285f 100644 --- a/interface/src/app/main/Scheduler.tsx +++ b/interface/src/app/main/Scheduler.tsx @@ -1,5 +1,4 @@ import { useCallback, useEffect, useState } from 'react'; -import type { FC } from 'react'; import { useBlocker } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -29,13 +28,13 @@ import { } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import * as EMSESP from './api'; import SettingsSchedulerDialog from './SchedulerDialog'; +import { readSchedule, writeSchedule } from './api'; import { ScheduleFlag } from './types'; import type { Schedule, ScheduleItem } from './types'; import { schedulerItemValidation } from './validators'; -const Scheduler: FC = () => { +const Scheduler = () => { const { LL, locale } = useI18nContext(); const [numChanges, setNumChanges] = useState(0); const blocker = useBlocker(numChanges !== 0); @@ -48,13 +47,12 @@ const Scheduler: FC = () => { data: schedule, send: fetchSchedule, error - } = useRequest(EMSESP.readSchedule, { - initialData: [], - force: true + } = useRequest(readSchedule, { + initialData: [] }); - const { send: writeSchedule } = useRequest( - (data: Schedule) => EMSESP.writeSchedule(data), + const { send: updateSchedule } = useRequest( + (data: Schedule) => writeSchedule(data), { immediate: false } @@ -131,7 +129,7 @@ const Scheduler: FC = () => { }); const saveSchedule = async () => { - await writeSchedule({ + await updateSchedule({ schedule: schedule .filter((si) => !si.deleted) .map((condensed_si) => ({ @@ -177,7 +175,7 @@ const Scheduler: FC = () => { const onDialogSave = (updatedItem: ScheduleItem) => { setDialogOpen(false); - updateState('schedule', (data: ScheduleItem[]) => { + void updateState(readSchedule(), (data: ScheduleItem[]) => { const new_data = creating ? [ ...data.filter((si) => creating || si.o_id !== updatedItem.o_id), diff --git a/interface/src/app/main/SchedulerDialog.tsx b/interface/src/app/main/SchedulerDialog.tsx index cb25db44c..7769bac00 100644 --- a/interface/src/app/main/SchedulerDialog.tsx +++ b/interface/src/app/main/SchedulerDialog.tsx @@ -208,7 +208,7 @@ const SchedulerDialog = ({ scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE ? 'primary' : 'grey' } > - {LL.ONCHANGE(0)} + {LL.ONCHANGE()} @@ -218,7 +218,7 @@ const SchedulerDialog = ({ scheduleType === ScheduleFlag.SCHEDULE_CONDITION ? 'primary' : 'grey' } > - {LL.CONDITION(0)} + {LL.CONDITION()} @@ -228,7 +228,7 @@ const SchedulerDialog = ({ scheduleType === ScheduleFlag.SCHEDULE_IMMEDIATE ? 'primary' : 'grey' } > - {LL.IMMEDIATE(0)} + {LL.IMMEDIATE()} @@ -282,7 +282,7 @@ const SchedulerDialog = ({ {scheduleType === ScheduleFlag.SCHEDULE_DAY || - scheduleType === ScheduleFlag.SCHEDULE_TIMER ? ( + scheduleType === ScheduleFlag.SCHEDULE_TIMER ? ( <> { +const Sensors = () => { const { LL } = useI18nContext(); const { me } = useContext(AuthenticatedContext); diff --git a/interface/src/app/main/api.ts b/interface/src/app/main/api.ts index 752aa71cc..66c0f14f8 100644 --- a/interface/src/app/main/api.ts +++ b/interface/src/app/main/api.ts @@ -88,7 +88,6 @@ export const writeDeviceName = (data: { id: number; name: string }) => // SettingsScheduler export const readSchedule = () => alovaInstance.Get('/rest/schedule', { - name: 'schedule', transform(data) { return (data as Schedule).schedule.map((si: ScheduleItem) => ({ ...si, @@ -109,7 +108,6 @@ export const writeSchedule = (data: Schedule) => // Modules export const readModules = () => alovaInstance.Get('/rest/modules', { - name: 'modules', transform(data) { return (data as Modules).modules.map((mi: ModuleItem) => ({ ...mi, @@ -127,7 +125,6 @@ export const writeModules = (data: { // SettingsEntities export const readCustomEntities = () => alovaInstance.Get('/rest/customEntities', { - name: 'entities', transform(data) { return (data as Entities).entities.map((ei: EntityItem) => ({ ...ei, diff --git a/interface/src/app/settings/APSettings.tsx b/interface/src/app/settings/APSettings.tsx index d2a9accb4..2c828ff64 100644 --- a/interface/src/app/settings/APSettings.tsx +++ b/interface/src/app/settings/APSettings.tsx @@ -1,5 +1,4 @@ import { useState } from 'react'; -import type { FC } from 'react'; import CancelIcon from '@mui/icons-material/Cancel'; import WarningIcon from '@mui/icons-material/Warning'; @@ -29,7 +28,7 @@ export const isAPEnabled = ({ provision_mode }: APSettingsType) => provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED; -const APSettings: FC = () => { +const APSettings = () => { const { loadData, saving, diff --git a/interface/src/app/settings/ApplicationSettings.tsx b/interface/src/app/settings/ApplicationSettings.tsx index 134eea9ad..526acd409 100644 --- a/interface/src/app/settings/ApplicationSettings.tsx +++ b/interface/src/app/settings/ApplicationSettings.tsx @@ -1,5 +1,4 @@ import { useState } from 'react'; -import type { FC } from 'react'; import { toast } from 'react-toastify'; import CancelIcon from '@mui/icons-material/Cancel'; @@ -49,9 +48,9 @@ export function boardProfileSelectItems() { )); } -const ApplicationSettings: FC = () => { +const ApplicationSettings = () => { const { data: hardwareData } = useRequest(SystemApi.readHardwareStatus, { - force: true + initialData: { psram: false } }); const { @@ -84,19 +83,12 @@ const ApplicationSettings: FC = () => { const [fieldErrors, setFieldErrors] = useState(); - const { - loading: processingBoard, - send: readBoardProfile, - onSuccess: onSuccessBoardProfile - } = useRequest((boardProfile: string) => EMSESP.getBoardProfile(boardProfile), { - immediate: false - }); - - const { send: restartCommand } = useRequest(SystemApi.restart(), { - immediate: false - }); - - onSuccessBoardProfile((event) => { + const { loading: processingBoard, send: readBoardProfile } = useRequest( + (boardProfile: string) => EMSESP.getBoardProfile(boardProfile), + { + immediate: false + } + ).onSuccess((event) => { const response = event.data as Settings; updateDataValue({ ...data, @@ -113,6 +105,10 @@ const ApplicationSettings: FC = () => { }); }); + const { send: restartCommand } = useRequest(SystemApi.restart(), { + immediate: false + }); + const updateBoardProfile = async (board_profile: string) => { await readBoardProfile(board_profile).catch((error: Error) => { toast.error(error.message); diff --git a/interface/src/app/settings/MqttSettings.tsx b/interface/src/app/settings/MqttSettings.tsx index 6ccf77680..715bb1d2c 100644 --- a/interface/src/app/settings/MqttSettings.tsx +++ b/interface/src/app/settings/MqttSettings.tsx @@ -1,5 +1,4 @@ import { useState } from 'react'; -import type { FC } from 'react'; import CancelIcon from '@mui/icons-material/Cancel'; import WarningIcon from '@mui/icons-material/Warning'; @@ -31,7 +30,7 @@ import type { MqttSettingsType } from 'types'; import { numberValue, updateValueDirty, useRest } from 'utils'; import { createMqttSettingsValidator, validate } from 'validators'; -const MqttSettings: FC = () => { +const MqttSettings = () => { const { loadData, saving, diff --git a/interface/src/app/settings/NTPSettings.tsx b/interface/src/app/settings/NTPSettings.tsx index fe9eae1cb..9843ccc0d 100644 --- a/interface/src/app/settings/NTPSettings.tsx +++ b/interface/src/app/settings/NTPSettings.tsx @@ -1,11 +1,11 @@ import { useState } from 'react'; -import type { FC } from 'react'; import CancelIcon from '@mui/icons-material/Cancel'; import WarningIcon from '@mui/icons-material/Warning'; import { Button, Checkbox, MenuItem } from '@mui/material'; import * as NTPApi from 'api/ntp'; +import { readNTPSettings } from 'api/ntp'; import { updateState } from 'alova/client'; import type { ValidateFieldsError } from 'async-validator'; @@ -26,7 +26,7 @@ import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp'; import { TIME_ZONES, selectedTimeZone, timeZoneSelectItems } from './TZ'; -const NTPSettings: FC = () => { +const NTPSettings = () => { const { loadData, saving, @@ -72,9 +72,7 @@ const NTPSettings: FC = () => { const changeTimeZone = (event: React.ChangeEvent) => { updateFormValue(event); - - // TODO fix - updateState('ntpSettings', (settings: NTPSettingsType) => ({ + void updateState(readNTPSettings(), (settings: NTPSettingsType) => ({ ...settings, tz_label: event.target.value, tz_format: TIME_ZONES[event.target.value] diff --git a/interface/src/app/settings/Settings.tsx b/interface/src/app/settings/Settings.tsx index d4899113e..2ccb1b4e6 100644 --- a/interface/src/app/settings/Settings.tsx +++ b/interface/src/app/settings/Settings.tsx @@ -1,4 +1,4 @@ -import { type FC, useState } from 'react'; +import { useState } from 'react'; import AccessTimeIcon from '@mui/icons-material/AccessTime'; import CancelIcon from '@mui/icons-material/Cancel'; @@ -29,7 +29,7 @@ import { SectionContent, useLayoutTitle } from 'components'; import ListMenuItem from 'components/layout/ListMenuItem'; import { useI18nContext } from 'i18n/i18n-react'; -const Settings: FC = () => { +const Settings = () => { const { LL } = useI18nContext(); useLayoutTitle(LL.SETTINGS(0)); diff --git a/interface/src/app/settings/UploadDownload.tsx b/interface/src/app/settings/UploadDownload.tsx index 586e9f50a..9a6f26220 100644 --- a/interface/src/app/settings/UploadDownload.tsx +++ b/interface/src/app/settings/UploadDownload.tsx @@ -1,4 +1,4 @@ -import { type FC, useState } from 'react'; +import { useState } from 'react'; import { toast } from 'react-toastify'; import DownloadIcon from '@mui/icons-material/GetApp'; @@ -19,61 +19,59 @@ import { useI18nContext } from 'i18n/i18n-react'; import RestartMonitor from '../status/RestartMonitor'; -const UploadDownload: FC = () => { +const UploadDownload = () => { const { LL } = useI18nContext(); const [restarting, setRestarting] = useState(); const [md5, setMd5] = useState(); - const { send: getSettings, onSuccess: onSuccessGetSettings } = useRequest( - EMSESP.getSettings(), - { - immediate: false - } - ); - const { send: getCustomizations, onSuccess: onSuccessGetCustomizations } = - useRequest(EMSESP.getCustomizations(), { - immediate: false - }); - const { send: getEntities, onSuccess: onSuccessGetEntities } = useRequest( - EMSESP.getEntities(), - { - immediate: false - } - ); - const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest( - EMSESP.getSchedule(), - { - immediate: false - } - ); - const { send: getAPI, onSuccess: onGetAPI } = useRequest( - (data: APIcall) => EMSESP.API(data), - { - immediate: false - } - ); + const { send: getSettings } = useRequest(EMSESP.getSettings(), { + immediate: false + }).onSuccess((event) => { + saveFile(event.data, 'settings.json'); + }); + + const { send: getCustomizations } = useRequest(EMSESP.getCustomizations(), { + immediate: false + }).onSuccess((event) => { + saveFile(event.data, 'customizations.json'); + }); + + const { send: getEntities } = useRequest(EMSESP.getEntities(), { + immediate: false + }).onSuccess((event) => { + saveFile(event.data, 'entities.json'); + }); + + const { send: getSchedule } = useRequest(EMSESP.getSchedule(), { + immediate: false + }).onSuccess((event) => { + saveFile(event.data, 'schedule.json'); + }); + + const { send: getAPI } = useRequest((data: APIcall) => EMSESP.API(data), { + immediate: false + }).onSuccess((event) => { + saveFile( + event.data, + String(event.args[0].device) + '_' + String(event.args[0].entity) + '.txt' + ); + }); const { data: data, send: loadData, error - } = useRequest(SystemApi.readHardwareStatus, { force: true }); + } = useRequest(SystemApi.readHardwareStatus); - const { data: latestVersion } = useRequest(SystemApi.getStableVersion, { - immediate: true, - force: true - }); - - const { data: latestDevVersion } = useRequest(SystemApi.getDevVersion, { - immediate: true, - force: true - }); + // called immediately to get the latest version, on page load + const { data: latestVersion } = useRequest(SystemApi.getStableVersion); + const { data: latestDevVersion } = useRequest(SystemApi.getDevVersion); const STABLE_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/'; - const DEV_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/latest/'; - const STABLE_RELNOTES_URL = 'https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md'; + + const DEV_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/latest/'; const DEV_RELNOTES_URL = 'https://github.com/emsesp/EMS-ESP32/blob/dev/CHANGELOG_LATEST.md'; @@ -95,16 +93,11 @@ const UploadDownload: FC = () => { loading: isUploading, uploading: progress, send: sendUpload, - onSuccess: onSuccessUpload, abort: cancelUpload } = useRequest(SystemApi.uploadFile, { - immediate: false, - force: true - }); - - onSuccessUpload(({ data }) => { + immediate: false + }).onSuccess(({ data }) => { if (data) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument setMd5(data.md5); toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL()); } else { @@ -137,22 +130,6 @@ const UploadDownload: FC = () => { toast.info(LL.DOWNLOAD_SUCCESSFUL()); }; - onSuccessGetSettings((event) => { - saveFile(event.data, 'settings.json'); - }); - onSuccessGetCustomizations((event) => { - saveFile(event.data, 'customizations.json'); - }); - onSuccessGetEntities((event) => { - saveFile(event.data, 'entities.json'); - }); - onSuccessGetSchedule((event) => { - saveFile(event.data, 'schedule.json'); - }); - onGetAPI((event) => { - saveFile(event.data, event.args[0].device + '_' + event.args[0].entity + '.txt'); - }); - const downloadSettings = async () => { await getSettings().catch((error: Error) => { toast.error(error.message); @@ -211,11 +188,7 @@ const UploadDownload: FC = () => { @@ -236,7 +209,7 @@ const UploadDownload: FC = () => { ) ( {LL.DOWNLOAD(1)} diff --git a/interface/src/app/settings/network/Network.tsx b/interface/src/app/settings/network/Network.tsx index 0088a93b8..0a7feeb54 100644 --- a/interface/src/app/settings/network/Network.tsx +++ b/interface/src/app/settings/network/Network.tsx @@ -1,5 +1,4 @@ import { useCallback, useState } from 'react'; -import type { FC } from 'react'; import { Navigate, Route, Routes, useNavigate } from 'react-router-dom'; import { Tab } from '@mui/material'; @@ -12,7 +11,7 @@ import NetworkSettings from './NetworkSettings'; import { WiFiConnectionContext } from './WiFiConnectionContext'; import WiFiNetworkScanner from './WiFiNetworkScanner'; -const Network: FC = () => { +const Network = () => { const { LL } = useI18nContext(); useLayoutTitle(LL.SETTINGS_OF(LL.NETWORK(0))); diff --git a/interface/src/app/settings/network/NetworkSettings.tsx b/interface/src/app/settings/network/NetworkSettings.tsx index 4af4e6a3b..4fc22b358 100644 --- a/interface/src/app/settings/network/NetworkSettings.tsx +++ b/interface/src/app/settings/network/NetworkSettings.tsx @@ -1,5 +1,4 @@ import { useContext, useEffect, useState } from 'react'; -import type { FC } from 'react'; import { toast } from 'react-toastify'; import CancelIcon from '@mui/icons-material/Cancel'; @@ -48,7 +47,7 @@ import RestartMonitor from '../../status/RestartMonitor'; import { WiFiConnectionContext } from './WiFiConnectionContext'; import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector'; -const NetworkSettings: FC = () => { +const NetworkSettings = () => { const { LL } = useI18nContext(); const { selectedNetwork, deselectNetwork } = useContext(WiFiConnectionContext); @@ -80,19 +79,22 @@ const NetworkSettings: FC = () => { useEffect(() => { if (!initialized && data) { if (selectedNetwork) { - updateState('networkSettings', (current_data: NetworkSettingsType) => ({ - ssid: selectedNetwork.ssid, - bssid: selectedNetwork.bssid, - password: current_data ? current_data.password : '', - hostname: current_data?.hostname, - static_ip_config: false, - bandwidth20: false, - tx_power: 0, - nosleep: false, - enableMDNS: true, - enableCORS: false, - CORSOrigin: '*' - })); + void updateState( + NetworkApi.readNetworkSettings(), + (current_data: NetworkSettingsType) => ({ + ssid: selectedNetwork.ssid, + bssid: selectedNetwork.bssid, + password: current_data ? current_data.password : '', + hostname: current_data?.hostname, + static_ip_config: false, + bandwidth20: false, + tx_power: 0, + nosleep: false, + enableMDNS: true, + enableCORS: false, + CORSOrigin: '*' + }) + ); } setInitialized(true); } diff --git a/interface/src/app/settings/network/WiFiNetworkScanner.tsx b/interface/src/app/settings/network/WiFiNetworkScanner.tsx index 5a48fb7f2..a73cdea7a 100644 --- a/interface/src/app/settings/network/WiFiNetworkScanner.tsx +++ b/interface/src/app/settings/network/WiFiNetworkScanner.tsx @@ -1,5 +1,4 @@ import { useRef, useState } from 'react'; -import type { FC } from 'react'; import PermScanWifiIcon from '@mui/icons-material/PermScanWifi'; import { Button } from '@mui/material'; @@ -15,23 +14,28 @@ import WiFiNetworkSelector from './WiFiNetworkSelector'; const NUM_POLLS = 10; const POLLING_FREQUENCY = 1000; -const WiFiNetworkScanner: FC = () => { +const WiFiNetworkScanner = () => { const pollCount = useRef(0); const { LL } = useI18nContext(); const [errorMessage, setErrorMessage] = useState(); - const { send: scanNetworks, onComplete: onCompleteScanNetworks } = useRequest( - NetworkApi.scanNetworks - ); // is called on page load to start network scan - const { - data: networkList, - send: getNetworkList, - onSuccess: onSuccessNetworkList - } = useRequest(NetworkApi.listNetworks, { - immediate: false - }); + // is called on page load to start network scan + const { send: scanNetworks } = useRequest(NetworkApi.scanNetworks).onComplete( + () => { + pollCount.current = 0; + setErrorMessage(undefined); + void updateState(NetworkApi.listNetworks(), () => undefined); + void getNetworkList(); + } + ); - onSuccessNetworkList((event) => { + const { data: networkList, send: getNetworkList } = useRequest( + NetworkApi.listNetworks, + { + immediate: false + } + ).onSuccess((event) => { + // is called when network scan is completed if (!event.data) { const completedPollCount = pollCount.current + 1; if (completedPollCount < NUM_POLLS) { @@ -44,14 +48,6 @@ const WiFiNetworkScanner: FC = () => { } }); - onCompleteScanNetworks(() => { - pollCount.current = 0; - setErrorMessage(undefined); - // TODO fix - updateState('listNetworks', () => undefined); - void getNetworkList(); - }); - const renderNetworkScanner = () => { if (!networkList) { return ( diff --git a/interface/src/app/settings/network/WiFiNetworkSelector.tsx b/interface/src/app/settings/network/WiFiNetworkSelector.tsx index 9124a4678..974c81c3d 100644 --- a/interface/src/app/settings/network/WiFiNetworkSelector.tsx +++ b/interface/src/app/settings/network/WiFiNetworkSelector.tsx @@ -1,5 +1,4 @@ import { useContext } from 'react'; -import type { FC } from 'react'; import LockIcon from '@mui/icons-material/Lock'; import LockOpenIcon from '@mui/icons-material/LockOpen'; @@ -18,15 +17,11 @@ import type { Theme } from '@mui/material'; import { MessageBox } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import type { WiFiNetwork, WiFiNetworkList } from 'types'; +import type { WiFiNetwork } from 'types'; import { WiFiEncryptionType } from 'types'; import { WiFiConnectionContext } from './WiFiConnectionContext'; -interface WiFiNetworkSelectorProps { - networkList: WiFiNetworkList; -} - export const isNetworkOpen = ({ encryption_type }: WiFiNetwork) => encryption_type === WiFiEncryptionType.WIFI_AUTH_OPEN; @@ -62,7 +57,7 @@ const networkQualityHighlight = ({ rssi }: WiFiNetwork, theme: Theme) => { return theme.palette.success.main; }; -const WiFiNetworkSelector: FC = ({ networkList }) => { +function WiFiNetworkSelector({ networkList }) { const { LL } = useI18nContext(); const theme = useTheme(); @@ -100,6 +95,6 @@ const WiFiNetworkSelector: FC = ({ networkList }) => { } return {networkList.networks.map(renderNetwork)}; -}; +} export default WiFiNetworkSelector; diff --git a/interface/src/app/settings/security/GenerateToken.tsx b/interface/src/app/settings/security/GenerateToken.tsx index b24a497a5..f0bdcea50 100644 --- a/interface/src/app/settings/security/GenerateToken.tsx +++ b/interface/src/app/settings/security/GenerateToken.tsx @@ -1,5 +1,4 @@ import { useEffect } from 'react'; -import type { FC } from 'react'; import CloseIcon from '@mui/icons-material/Close'; import { @@ -21,12 +20,7 @@ import { useRequest } from 'alova/client'; import { MessageBox } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -interface GenerateTokenProps { - username?: string; - onClose: () => void; -} - -const GenerateToken: FC = ({ username, onClose }) => { +export default function GenerateToken({ username, onClose }) { const { LL } = useI18nContext(); const open = !!username; @@ -85,6 +79,4 @@ const GenerateToken: FC = ({ username, onClose }) => { ); -}; - -export default GenerateToken; +} diff --git a/interface/src/app/settings/security/ManageUsers.tsx b/interface/src/app/settings/security/ManageUsers.tsx index c49c20bc5..9805d8dcf 100644 --- a/interface/src/app/settings/security/ManageUsers.tsx +++ b/interface/src/app/settings/security/ManageUsers.tsx @@ -1,5 +1,4 @@ import { useContext, useState } from 'react'; -import type { FC } from 'react'; import { useBlocker } from 'react-router-dom'; import CancelIcon from '@mui/icons-material/Cancel'; @@ -40,7 +39,7 @@ import { createUserValidator } from 'validators'; import GenerateToken from './GenerateToken'; import User from './User'; -const ManageUsers: FC = () => { +const ManageUsers = () => { const { loadData, saveData, saving, data, updateDataValue, errorMessage } = useRest({ read: SecurityApi.readSecuritySettings, diff --git a/interface/src/app/settings/security/Security.tsx b/interface/src/app/settings/security/Security.tsx index 32d452af2..6bf2b3bc4 100644 --- a/interface/src/app/settings/security/Security.tsx +++ b/interface/src/app/settings/security/Security.tsx @@ -1,4 +1,3 @@ -import type { FC } from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; import { Tab } from '@mui/material'; @@ -9,7 +8,7 @@ import { useI18nContext } from 'i18n/i18n-react'; import ManageUsers from './ManageUsers'; import SecuritySettings from './SecuritySettings'; -const Security: FC = () => { +const Security = () => { const { LL } = useI18nContext(); useLayoutTitle(LL.SETTINGS_OF(LL.SECURITY(0))); diff --git a/interface/src/app/settings/security/SecuritySettings.tsx b/interface/src/app/settings/security/SecuritySettings.tsx index 2b49e17db..4106af2ac 100644 --- a/interface/src/app/settings/security/SecuritySettings.tsx +++ b/interface/src/app/settings/security/SecuritySettings.tsx @@ -1,5 +1,4 @@ import { useContext, useState } from 'react'; -import type { FC } from 'react'; import CancelIcon from '@mui/icons-material/Cancel'; import WarningIcon from '@mui/icons-material/Warning'; @@ -22,7 +21,7 @@ import type { SecuritySettingsType } from 'types'; import { updateValueDirty, useRest } from 'utils'; import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators'; -const SecuritySettings: FC = () => { +const SecuritySettings = () => { const { LL } = useI18nContext(); const [fieldErrors, setFieldErrors] = useState(); diff --git a/interface/src/app/settings/security/User.tsx b/interface/src/app/settings/security/User.tsx index 9d234abda..6860cff1d 100644 --- a/interface/src/app/settings/security/User.tsx +++ b/interface/src/app/settings/security/User.tsx @@ -29,10 +29,8 @@ import { validate } from 'validators'; interface UserFormProps { creating: boolean; validator: Schema; - user?: UserType; setUser: React.Dispatch>; - onDoneEditing: () => void; onCancelEditing: () => void; } diff --git a/interface/src/app/status/APStatus.tsx b/interface/src/app/status/APStatus.tsx index 4f70191d2..44aa25e2e 100644 --- a/interface/src/app/status/APStatus.tsx +++ b/interface/src/app/status/APStatus.tsx @@ -1,5 +1,3 @@ -import type { FC } from 'react'; - import ComputerIcon from '@mui/icons-material/Computer'; import DeviceHubIcon from '@mui/icons-material/DeviceHub'; 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 { LL } = useI18nContext(); diff --git a/interface/src/app/status/Activity.tsx b/interface/src/app/status/Activity.tsx index 0e10b6c09..94bf4698f 100644 --- a/interface/src/app/status/Activity.tsx +++ b/interface/src/app/status/Activity.tsx @@ -1,5 +1,4 @@ import { useEffect } from 'react'; -import type { FC } from 'react'; import RefreshIcon from '@mui/icons-material/Refresh'; import { Button } from '@mui/material'; @@ -22,7 +21,7 @@ import type { Translation } from 'i18n/i18n-types'; import * as EMSESP from '../main/api'; import type { Stat } from '../main/types'; -const SystemActivity: FC = () => { +const SystemActivity = () => { const { data: data, send: loadData, error } = useRequest(EMSESP.readActivity); const { LL } = useI18nContext(); diff --git a/interface/src/app/status/HardwareStatus.tsx b/interface/src/app/status/HardwareStatus.tsx index 4b904f65c..32c440d5f 100644 --- a/interface/src/app/status/HardwareStatus.tsx +++ b/interface/src/app/status/HardwareStatus.tsx @@ -1,5 +1,3 @@ -import type { FC } from 'react'; - import AppsIcon from '@mui/icons-material/Apps'; import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard'; import DevicesIcon from '@mui/icons-material/Devices'; @@ -32,7 +30,7 @@ function formatNumber(num: number) { return new Intl.NumberFormat().format(num); } -const HardwareStatus: FC = () => { +const HardwareStatus = () => { const { LL } = useI18nContext(); useLayoutTitle(LL.STATUS_OF(LL.HARDWARE())); @@ -41,7 +39,7 @@ const HardwareStatus: FC = () => { data: data, send: loadData, error - } = useRequest(SystemApi.readHardwareStatus, { force: true }); + } = useRequest(SystemApi.readHardwareStatus); const content = () => { if (!data) { diff --git a/interface/src/app/status/MqttStatus.tsx b/interface/src/app/status/MqttStatus.tsx index fe0029c47..462d17130 100644 --- a/interface/src/app/status/MqttStatus.tsx +++ b/interface/src/app/status/MqttStatus.tsx @@ -1,5 +1,3 @@ -import type { FC } from 'react'; - import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion'; import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import RefreshIcon from '@mui/icons-material/Refresh'; @@ -57,7 +55,7 @@ export const mqttQueueHighlight = ( return theme.palette.warning.main; }; -const MqttStatus: FC = () => { +const MqttStatus = () => { const { data: data, send: loadData, error } = useRequest(MqttApi.readMqttStatus); const { LL } = useI18nContext(); diff --git a/interface/src/app/status/NTPStatus.tsx b/interface/src/app/status/NTPStatus.tsx index 16ecf6bb1..13b7841fe 100644 --- a/interface/src/app/status/NTPStatus.tsx +++ b/interface/src/app/status/NTPStatus.tsx @@ -1,5 +1,4 @@ import { useState } from 'react'; -import type { FC } from 'react'; import { toast } from 'react-toastify'; import AccessTimeIcon from '@mui/icons-material/AccessTime'; @@ -37,7 +36,7 @@ import type { NTPStatusType, Time } from 'types'; import { NTPSyncStatus } from 'types'; import { formatDateTime, formatLocalDateTime } from 'utils'; -const NTPStatus: FC = () => { +const NTPStatus = () => { const { data: data, send: loadData, error } = useRequest(NTPApi.readNTPStatus); const [localTime, setLocalTime] = useState(''); diff --git a/interface/src/app/status/NetworkStatus.tsx b/interface/src/app/status/NetworkStatus.tsx index ca9f39a93..201193cb2 100644 --- a/interface/src/app/status/NetworkStatus.tsx +++ b/interface/src/app/status/NetworkStatus.tsx @@ -1,5 +1,3 @@ -import type { FC } from 'react'; - import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import DnsIcon from '@mui/icons-material/Dns'; import GiteIcon from '@mui/icons-material/Gite'; @@ -84,7 +82,7 @@ const IPs = (status: NetworkStatusType) => { return status.local_ip + ', ' + status.local_ipv6; }; -const NetworkStatus: FC = () => { +const NetworkStatus = () => { const { data: data, send: loadData, diff --git a/interface/src/app/status/RestartMonitor.tsx b/interface/src/app/status/RestartMonitor.tsx index 6d5aa5f67..a8cb12774 100644 --- a/interface/src/app/status/RestartMonitor.tsx +++ b/interface/src/app/status/RestartMonitor.tsx @@ -1,5 +1,4 @@ import { useEffect, useRef, useState } from 'react'; -import type { FC } from 'react'; import * as SystemApi from 'api/system'; @@ -10,11 +9,11 @@ import { useI18nContext } from 'i18n/i18n-react'; const RESTART_TIMEOUT = 2 * 60 * 1000; const POLL_INTERVAL = 3000; -const RestartMonitor: FC = () => { +const RestartMonitor = () => { const [failed, setFailed] = useState(false); const [timeoutId, setTimeoutId] = useState(); const { LL } = useI18nContext(); - const { send } = useRequest(SystemApi.readSystemStatus, { force: true }); + const { send } = useRequest(SystemApi.readSystemStatus); const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT); const poll = useRef(async () => { diff --git a/interface/src/app/status/Status.tsx b/interface/src/app/status/Status.tsx index 355322fd5..10ad173e8 100644 --- a/interface/src/app/status/Status.tsx +++ b/interface/src/app/status/Status.tsx @@ -1,4 +1,4 @@ -import { type FC, useContext, useState } from 'react'; +import { useContext, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -44,7 +44,7 @@ import { NTPSyncStatus, NetworkConnectionStatus } from 'types'; import RestartMonitor from './RestartMonitor'; -const SystemStatus: FC = () => { +const SystemStatus = () => { const { LL } = useI18nContext(); const navigate = useNavigate(); @@ -272,12 +272,13 @@ const SystemStatus: FC = () => { ); const content = () => { - // if (!data) { - // return ; - // } - + // TODO remove test code if (loading) { - return <>fddfdd; + return <>not loaded!; + } + + if (!data) { + return ; } return ( @@ -411,7 +412,9 @@ const SystemStatus: FC = () => { ); }; - return {content()}; + return ( + {restarting ? : content()} + ); }; export default SystemStatus; diff --git a/interface/src/app/status/SystemLog.tsx b/interface/src/app/status/SystemLog.tsx index 3fa10f569..32d649e6e 100644 --- a/interface/src/app/status/SystemLog.tsx +++ b/interface/src/app/status/SystemLog.tsx @@ -1,5 +1,4 @@ import { useEffect, useRef, useState } from 'react'; -import type { FC } from 'react'; import { toast } from 'react-toastify'; import DownloadIcon from '@mui/icons-material/GetApp'; @@ -71,7 +70,7 @@ const levelLabel = (level: LogLevel) => { } }; -const SystemLog: FC = () => { +const SystemLog = () => { const { LL } = useI18nContext(); useLayoutTitle(LL.LOG_OF(LL.SYSTEM(0))); diff --git a/interface/src/components/inputs/LanguageSelector.tsx b/interface/src/components/inputs/LanguageSelector.tsx index 6a1837c2f..ac049308a 100644 --- a/interface/src/components/inputs/LanguageSelector.tsx +++ b/interface/src/components/inputs/LanguageSelector.tsx @@ -1,4 +1,4 @@ -import { type ChangeEventHandler, type FC, useContext } from 'react'; +import { type ChangeEventHandler, useContext } from 'react'; import { MenuItem, TextField } from '@mui/material'; @@ -16,7 +16,7 @@ import { I18nContext } from 'i18n/i18n-react'; import type { Locales } from 'i18n/i18n-types'; import { loadLocaleAsync } from 'i18n/i18n-util.async'; -const LanguageSelector: FC = () => { +const LanguageSelector = () => { const { setLocale, locale } = useContext(I18nContext); const onLocaleSelected: ChangeEventHandler = async ({ diff --git a/interface/src/components/layout/LayoutAppBar.tsx b/interface/src/components/layout/LayoutAppBar.tsx index 06e43e798..46de4aa7d 100644 --- a/interface/src/components/layout/LayoutAppBar.tsx +++ b/interface/src/components/layout/LayoutAppBar.tsx @@ -1,4 +1,3 @@ -import type { FC } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; @@ -7,12 +6,7 @@ import { AppBar, IconButton, Toolbar, Typography } from '@mui/material'; export const DRAWER_WIDTH = 210; -interface LayoutAppBarProps { - title: string; - onToggleDrawer: () => void; -} - -const LayoutAppBar: FC = ({ title, onToggleDrawer }) => { +export default function LayoutAppBar({ title, onToggleDrawer }) { const pathnames = useLocation() .pathname.split('/') .filter((x) => x); @@ -56,6 +50,4 @@ const LayoutAppBar: FC = ({ title, onToggleDrawer }) => { ); -}; - -export default LayoutAppBar; +} diff --git a/interface/src/components/layout/LayoutDrawer.tsx b/interface/src/components/layout/LayoutDrawer.tsx index b1cd4f254..645fd4182 100644 --- a/interface/src/components/layout/LayoutDrawer.tsx +++ b/interface/src/components/layout/LayoutDrawer.tsx @@ -1,5 +1,3 @@ -import type { FC } from 'react'; - import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material'; import { PROJECT_NAME } from 'api/env'; @@ -18,12 +16,7 @@ const LayoutDrawerLogo = styled('img')(({ theme }) => ({ } })); -interface LayoutDrawerProps { - mobileOpen: boolean; - onClose: () => void; -} - -const LayoutDrawer: FC = ({ mobileOpen, onClose }) => { +export default function LayoutDrawerProps({ mobileOpen, onClose }) { const drawer = ( <> @@ -66,6 +59,4 @@ const LayoutDrawer: FC = ({ mobileOpen, onClose }) => { ); -}; - -export default LayoutDrawer; +} diff --git a/interface/src/components/layout/LayoutMenu.tsx b/interface/src/components/layout/LayoutMenu.tsx index f3efb398d..734010a3d 100644 --- a/interface/src/components/layout/LayoutMenu.tsx +++ b/interface/src/components/layout/LayoutMenu.tsx @@ -1,5 +1,4 @@ import { useContext, useState } from 'react'; -import type { FC } from 'react'; import AccountCircleIcon from '@mui/icons-material/AccountCircle'; import AssessmentIcon from '@mui/icons-material/Assessment'; @@ -30,7 +29,7 @@ import LayoutMenuItem from 'components/layout/LayoutMenuItem'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; -const LayoutMenu: FC = () => { +const LayoutMenu = () => { const { me, signOut } = useContext(AuthenticatedContext); const { LL } = useI18nContext(); diff --git a/interface/src/components/loading/ApplicationError.tsx b/interface/src/components/loading/ApplicationError.tsx deleted file mode 100644 index 94a0db9fc..000000000 --- a/interface/src/components/loading/ApplicationError.tsx +++ /dev/null @@ -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 = ({ message }) => ( - - - - - - Application Error - - - - Failed to configure the application, please refresh to try again. - - {message && ( - - {message} - - )} - - -); - -export default ApplicationError; diff --git a/interface/src/components/loading/index.ts b/interface/src/components/loading/index.ts index f8c7b8608..76041e14b 100644 --- a/interface/src/components/loading/index.ts +++ b/interface/src/components/loading/index.ts @@ -1,3 +1,2 @@ -export { default as ApplicationError } from './ApplicationError'; export { default as LoadingSpinner } from './LoadingSpinner'; export { default as FormLoader } from './FormLoader'; diff --git a/interface/src/components/routing/BlockNavigation.tsx b/interface/src/components/routing/BlockNavigation.tsx index 361da67d7..d75683061 100644 --- a/interface/src/components/routing/BlockNavigation.tsx +++ b/interface/src/components/routing/BlockNavigation.tsx @@ -1,6 +1,3 @@ -import type { FC } from 'react'; -import type { Blocker } from 'react-router-dom'; - import { Button, Dialog, @@ -12,11 +9,7 @@ import { import { dialogStyle } from 'CustomTheme'; import { useI18nContext } from 'i18n/i18n-react'; -interface BlockNavigationProps { - blocker: Blocker; -} - -const BlockNavigation: FC = ({ blocker }) => { +export default function BlockNavigation({ blocker }) { const { LL } = useI18nContext(); return ( @@ -41,6 +34,4 @@ const BlockNavigation: FC = ({ blocker }) => { ); -}; - -export default BlockNavigation; +} diff --git a/interface/src/components/upload/SingleUpload.tsx b/interface/src/components/upload/SingleUpload.tsx index 9eae681b5..74cd66b56 100644 --- a/interface/src/components/upload/SingleUpload.tsx +++ b/interface/src/components/upload/SingleUpload.tsx @@ -8,7 +8,7 @@ import CloudUploadIcon from '@mui/icons-material/CloudUpload'; import { Box, Button, LinearProgress, Typography, useTheme } 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'; const getBorderColor = (theme: Theme, props: DropzoneState) => { diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index 60997c25e..8fa77ef12 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -252,9 +252,9 @@ const en: Translation = { TIME_ZONE: 'Time Zone', ACCESS_POINT: 'Access Point', AP_PROVIDE: 'Enable Access Point', - AP_PROVIDE_TEXT_1: 'always', - AP_PROVIDE_TEXT_2: 'when WiFi is disconnected', - AP_PROVIDE_TEXT_3: 'never', + AP_PROVIDE_TEXT_1: 'Always', + AP_PROVIDE_TEXT_2: 'When WiFi is disconnected', + AP_PROVIDE_TEXT_3: 'Never', AP_PREFERRED_CHANNEL: 'Preferred Channel', AP_HIDE_SSID: 'Hide SSID', AP_CLIENTS: 'AP Clients', diff --git a/interface/src/utils/useRest.ts b/interface/src/utils/useRest.ts index 97695e922..fac7acaed 100644 --- a/interface/src/utils/useRest.ts +++ b/interface/src/utils/useRest.ts @@ -24,30 +24,22 @@ export const useRest = ({ read, update }: RestRequestOptions) => { const { data, send: readData, - update: updateData, - // TODO refactor - onComplete: onReadComplete - } = useRequest(read()); + update: updateData + } = useRequest(read()).onComplete((event) => { + setOrigData(event.data as D); + }); - const { - loading: saving, - send: writeData, - // TODO refactor - onSuccess: onWriteSuccess - } = useRequest((newData: D) => update(newData), { immediate: false }); - - const updateDataValue = (new_data: D) => { - updateData({ data: new_data }); - }; - - onWriteSuccess(() => { + const { loading: saving, send: writeData } = useRequest( + (newData: D) => update(newData), + { immediate: false } + ).onSuccess(() => { toast.success(LL.UPDATED_OF(LL.SETTINGS(1))); setDirtyFlags([]); }); - onReadComplete((event) => { - setOrigData(event.data as D); - }); + const updateDataValue = (new_data: D) => { + updateData({ data: new_data }); + }; const loadData = async () => { setDirtyFlags([]); diff --git a/interface/src/validators/shared.ts b/interface/src/validators/shared.ts index 4c9d5c23b..b297f30a9 100644 --- a/interface/src/validators/shared.ts +++ b/interface/src/validators/shared.ts @@ -12,7 +12,7 @@ export const validate = ( options ? options : {}, (errors, fieldErrors) => { if (errors) { - reject(fieldErrors); + reject(fieldErrors as Error); } else { resolve(source as T); } diff --git a/interface/yarn.lock b/interface/yarn.lock index c938f970a..fa038c152 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -1705,13 +1705,13 @@ __metadata: react-router-dom: "npm:^6.26.0" react-toastify: "npm:^10.0.5" rollup-plugin-visualizer: "npm:^5.12.0" - terser: "npm:^5.31.3" + terser: "npm:^5.31.5" typesafe-i18n: "npm:^5.26.2" typescript: "npm:^5.5.4" typescript-eslint: "npm:8.0.1" - vite: "npm:^5.3.5" + vite: "npm:^5.4.0" vite-plugin-imagemin: "npm:^0.6.1" - vite-tsconfig-paths: "npm:^4.3.2" + vite-tsconfig-paths: "npm:^5.0.0" languageName: unknown linkType: soft @@ -5611,14 +5611,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.39": - version: 8.4.40 - resolution: "postcss@npm:8.4.40" +"postcss@npm:^8.4.40": + version: 8.4.41 + resolution: "postcss@npm:8.4.41" dependencies: nanoid: "npm:^3.3.7" picocolors: "npm:^1.0.1" source-map-js: "npm:^1.2.0" - checksum: 10c0/65ed67573e5443beaeb582282ff27a6be7c7fe3b4d9fa15761157616f2b97510cb1c335023c26220b005909f007337026d6e3ff092f25010b484ad484e80ea7f + checksum: 10c0/c1828fc59e7ec1a3bf52b3a42f615dba53c67960ed82a81df6441b485fe43c20aba7f4e7c55425762fd99c594ecabbaaba8cf5b30fd79dfec5b52a9f63a2d690 languageName: node linkType: hard @@ -6686,9 +6686,9 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.31.3": - version: 5.31.3 - resolution: "terser@npm:5.31.3" +"terser@npm:^5.31.5": + version: 5.31.5 + resolution: "terser@npm:5.31.5" dependencies: "@jridgewell/source-map": "npm:^0.3.3" acorn: "npm:^8.8.2" @@ -6696,7 +6696,7 @@ __metadata: source-map-support: "npm:~0.5.20" bin: terser: bin/terser - checksum: 10c0/eb2b525dada9febd3db74e94bd295f9cd7abd809e4f9c6bbc795a3048ad50fd327c15eab99db383fa820239680eef6d2dbd7dc05361769c204ddee5cf684d41e + checksum: 10c0/6e7c66c1f4062ee098bff3dc3c396819ebf5f1740f0615be9de39b675a78c732d199f4dcfdcd15bd65f354e37c45bb944360f532a36fe7f7d22f800ca53c2d02 languageName: node linkType: hard @@ -7025,9 +7025,9 @@ __metadata: languageName: node linkType: hard -"vite-tsconfig-paths@npm:^4.3.2": - version: 4.3.2 - resolution: "vite-tsconfig-paths@npm:4.3.2" +"vite-tsconfig-paths@npm:^5.0.0": + version: 5.0.0 + resolution: "vite-tsconfig-paths@npm:5.0.0" dependencies: debug: "npm:^4.1.1" globrex: "npm:^0.1.2" @@ -7037,23 +7037,24 @@ __metadata: peerDependenciesMeta: vite: optional: true - checksum: 10c0/f390ac1d1c3992fc5ac50f9274c1090f8b55ab34a89ea88893db9a6924a3b26c9f64bc1163615150ad100749db73b6b2cf1d57f6cd60df6e762ceb5b8ad30024 + checksum: 10c0/c29284abb92e829558f4913fb4e6e8ee2581e90c0ad13d6c88f4c16250d03cde2d14729af63df76d801babfc3ee9c6ccff5a421142d7af4f497beab988e9196c languageName: node linkType: hard -"vite@npm:^5.3.5": - version: 5.3.5 - resolution: "vite@npm:5.3.5" +"vite@npm:^5.4.0": + version: 5.4.0 + resolution: "vite@npm:5.4.0" dependencies: esbuild: "npm:^0.21.3" fsevents: "npm:~2.3.3" - postcss: "npm:^8.4.39" + postcss: "npm:^8.4.40" rollup: "npm:^4.13.0" peerDependencies: "@types/node": ^18.0.0 || >=20.0.0 less: "*" lightningcss: ^1.21.0 sass: "*" + sass-embedded: "*" stylus: "*" sugarss: "*" terser: ^5.4.0 @@ -7069,6 +7070,8 @@ __metadata: optional: true sass: optional: true + sass-embedded: + optional: true stylus: optional: true sugarss: @@ -7077,7 +7080,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/795c7e0dbc94b96c4a0aff0d5d4b349dd28ad8b7b70979c1010f96b4d83f7d6c1700ebd6fed91de2e021b0a3689b9abc2d8017f6dfa8c9a6ca5c7af637d6afc6 + checksum: 10c0/122de7795e1c3c08cd0acc7d77296f908398266b424492be7310400107f37a3cf4c9506f2b4b16619e57299ca2859b8ca187aac5e25f8e66d84f9204a1d72d18 languageName: node linkType: hard diff --git a/mock-api/rest_server.ts b/mock-api/rest_server.ts index 16ef11f18..fac17745a 100644 --- a/mock-api/rest_server.ts +++ b/mock-api/rest_server.ts @@ -4356,7 +4356,7 @@ router ) .get(EMSESP_DEVICEENTITIES_ENDPOINT2, ({ params }) => (params.id ? deviceEntities(Number(params.id)) : status(404))) - // Customization + // Customizations .post(EMSESP_CUSTOMIZATION_ENTITIES_ENDPOINT, async (request: any) => { const content = await request.json(); const id = content.id; diff --git a/src/web/WebCustomizationService.cpp b/src/web/WebCustomizationService.cpp index 86239f466..da96e6611 100644 --- a/src/web/WebCustomizationService.cpp +++ b/src/web/WebCustomizationService.cpp @@ -210,7 +210,7 @@ void WebCustomizationService::device_entities(AsyncWebServerRequest * request) { #endif #if defined(EMSESP_DEBUG) size_t length = response->setLength(); - EMSESP::logger().debug("Customization buffer used: %d", length); + EMSESP::logger().debug("Customizations buffer used: %d", length); #else response->setLength(); #endif