From 8e081ce04f8dddb1317f89538dc6909b58df9ad9 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 11 Jun 2023 20:31:23 +0200 Subject: [PATCH] alova update #6 --- interface/package.json | 8 +- interface/src/api/ap.ts | 6 +- interface/src/api/endpoints.ts | 13 ++- interface/src/framework/ap/APStatusForm.tsx | 7 +- .../src/framework/mqtt/MqttStatusForm.tsx | 2 + .../framework/network/NetworkStatusForm.tsx | 2 + interface/src/framework/ntp/NTPStatusForm.tsx | 2 + interface/src/framework/system/SystemLog.tsx | 2 + .../src/framework/system/SystemStatusForm.tsx | 2 + interface/src/i18n/de/index.ts | 1 - interface/src/i18n/en/index.ts | 1 - interface/src/i18n/fr/index.ts | 1 - interface/src/i18n/nl/index.ts | 1 - interface/src/i18n/no/index.ts | 1 - interface/src/i18n/pl/index.ts | 1 - interface/src/i18n/sv/index.ts | 1 - interface/src/i18n/tr/index.ts | 1 - interface/src/project/DashboardDevices.tsx | 13 +-- interface/src/project/DashboardStatus.tsx | 2 + interface/src/project/SettingsApplication.tsx | 10 +- interface/src/project/api.ts | 18 ++-- interface/src/project/types.ts | 7 -- interface/src/utils/binding.ts | 14 ++- interface/src/utils/index.ts | 2 + interface/src/utils/useRest2.ts | 92 +++++++++++++++++++ interface/yarn.lock | 50 +++++----- mock-api/server.js | 5 +- src/version.h | 2 +- src/web/WebDataService.cpp | 69 ++------------ src/web/WebDataService.h | 15 +-- 30 files changed, 191 insertions(+), 160 deletions(-) create mode 100644 interface/src/utils/useRest2.ts diff --git a/interface/package.json b/interface/package.json index 49f86eb8f..c72938554 100644 --- a/interface/package.json +++ b/interface/package.json @@ -26,8 +26,8 @@ "@mui/material": "^5.13.4", "@table-library/react-table-library": "4.1.4", "@types/lodash-es": "^4.17.7", - "@types/node": "^20.2.5", - "@types/react": "^18.2.9", + "@types/node": "^20.3.0", + "@types/react": "^18.2.11", "@types/react-dom": "^18.2.4", "@types/react-router-dom": "^5.3.3", "alova": "^2.6.0", @@ -40,7 +40,7 @@ "react-dom": "latest", "react-dropzone": "^14.2.3", "react-icons": "^4.9.0", - "react-router-dom": "^6.12.0", + "react-router-dom": "^6.12.1", "react-toastify": "^9.1.3", "sockette": "^2.0.6", "typesafe-i18n": "^5.24.3", @@ -64,7 +64,7 @@ "nodemon": "^2.0.22", "npm-run-all": "^4.1.5", "prettier": "^2.8.8", - "rollup-plugin-visualizer": "^5.9.0", + "rollup-plugin-visualizer": "^5.9.2", "terser": "^5.17.7", "vite": "^4.3.9", "vite-plugin-svgr": "^3.2.0", diff --git a/interface/src/api/ap.ts b/interface/src/api/ap.ts index 734a40f64..f369d1b61 100644 --- a/interface/src/api/ap.ts +++ b/interface/src/api/ap.ts @@ -1,11 +1,9 @@ -import { AXIOS } from './endpoints'; +import { AXIOS, alovaInstance } from './endpoints'; import type { AxiosPromise } from 'axios'; import type { APSettings, APStatus } from 'types'; -export function readAPStatus(): AxiosPromise { - return AXIOS.get('/apStatus'); -} +export const readAPStatus = () => alovaInstance.Get('/apStatus'); export function readAPSettings(): AxiosPromise { return AXIOS.get('/apSettings'); diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index 229e00a27..ced2bc964 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -1,6 +1,5 @@ import { xhrRequestAdapter } from '@alova/adapter-xhr'; import { createAlova } from 'alova'; -// import GlobalFetch from 'alova/GlobalFetch'; import ReactHook from 'alova/react'; import axios from 'axios'; import { unpack } from '../api/unpack'; @@ -23,21 +22,21 @@ export const EVENT_SOURCE_ROOT = location.protocol + '//' + location.host + ES_B export const alovaInstance = createAlova({ baseURL: '/rest/', statesHook: ReactHook, + timeout: 50000, requestAdapter: xhrRequestAdapter(), - // requestAdapter: GlobalFetch(), beforeRequest(method) { - // TODO check if bearer works if (localStorage.getItem(ACCESS_TOKEN)) { - method.config.headers.token = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN); + method.config.headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN); } }, responded: { onSuccess: async (response) => { - if (response.status === 400) { + if (response.status == 202) { + throw new Error('Reboot required'); + } else if (response.status === 400) { throw new Error('Invalid command'); - } - if (response.status >= 400) { + } else if (response.status >= 400) { throw new Error(response.statusText); } const data = await response.data; diff --git a/interface/src/framework/ap/APStatusForm.tsx b/interface/src/framework/ap/APStatusForm.tsx index a4cfb6172..be15f9f7f 100644 --- a/interface/src/framework/ap/APStatusForm.tsx +++ b/interface/src/framework/ap/APStatusForm.tsx @@ -3,6 +3,7 @@ import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import RefreshIcon from '@mui/icons-material/Refresh'; import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material'; +import { useRequest } from 'alova'; import type { Theme } from '@mui/material'; import type { FC } from 'react'; @@ -12,7 +13,6 @@ import { ButtonRow, FormLoader, SectionContent } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import { APNetworkStatus } from 'types'; -import { useRest } from 'utils'; export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => { switch (status) { @@ -28,7 +28,8 @@ export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => { }; const APStatusForm: FC = () => { - const { loadData, data, errorMessage } = useRest({ read: APApi.readAPStatus }); + // TODO missing update! + const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus()); const { LL } = useI18nContext(); @@ -49,7 +50,7 @@ const APStatusForm: FC = () => { const content = () => { if (!data) { - return ; + return ; } return ( diff --git a/interface/src/framework/mqtt/MqttStatusForm.tsx b/interface/src/framework/mqtt/MqttStatusForm.tsx index b8a20f915..f7af8bd88 100644 --- a/interface/src/framework/mqtt/MqttStatusForm.tsx +++ b/interface/src/framework/mqtt/MqttStatusForm.tsx @@ -39,6 +39,8 @@ export const mqttQueueHighlight = ({ mqtt_queued }: MqttStatus, theme: Theme) => }; const MqttStatusForm: FC = () => { + // TODO missing update! + const { loadData, data, errorMessage } = useRest({ read: MqttApi.readMqttStatus }); const { LL } = useI18nContext(); diff --git a/interface/src/framework/network/NetworkStatusForm.tsx b/interface/src/framework/network/NetworkStatusForm.tsx index f71f1aa0e..93d1ce840 100644 --- a/interface/src/framework/network/NetworkStatusForm.tsx +++ b/interface/src/framework/network/NetworkStatusForm.tsx @@ -59,6 +59,8 @@ const IPs = (status: NetworkStatus) => { }; const NetworkStatusForm: FC = () => { + // TODO missing update! + const { loadData, data, errorMessage } = useRest({ read: NetworkApi.readNetworkStatus }); const { LL } = useI18nContext(); diff --git a/interface/src/framework/ntp/NTPStatusForm.tsx b/interface/src/framework/ntp/NTPStatusForm.tsx index 51fb5a640..903ee6025 100644 --- a/interface/src/framework/ntp/NTPStatusForm.tsx +++ b/interface/src/framework/ntp/NTPStatusForm.tsx @@ -52,6 +52,8 @@ export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => { }; const NTPStatusForm: FC = () => { + // TODO missing update! + const { loadData, data, errorMessage } = useRest({ read: NTPApi.readNTPStatus }); const [localTime, setLocalTime] = useState(''); const [settingTime, setSettingTime] = useState(false); diff --git a/interface/src/framework/system/SystemLog.tsx b/interface/src/framework/system/SystemLog.tsx index 68a7f6cdf..dffcdeb0b 100644 --- a/interface/src/framework/system/SystemLog.tsx +++ b/interface/src/framework/system/SystemLog.tsx @@ -49,6 +49,8 @@ const levelLabel = (level: LogLevel) => { const SystemLog: FC = () => { const { LL } = useI18nContext(); + // TODO missing update! + const { loadData, data, setData, origData, dirtyFlags, blocker, setDirtyFlags, setOrigData } = useRest({ read: SystemApi.readLogSettings }); diff --git a/interface/src/framework/system/SystemStatusForm.tsx b/interface/src/framework/system/SystemStatusForm.tsx index 792e5df79..f7f8043a3 100644 --- a/interface/src/framework/system/SystemStatusForm.tsx +++ b/interface/src/framework/system/SystemStatusForm.tsx @@ -53,6 +53,8 @@ const SystemStatusForm: FC = () => { const { LL } = useI18nContext(); const [restarting, setRestarting] = useState(); + // TODO missing update! + const { loadData, data, errorMessage } = useRest({ read: SystemApi.readSystemStatus }); const { me } = useContext(AuthenticatedContext); diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index 89a113724..3acac78c8 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -51,7 +51,6 @@ const de: Translation = { REMOVE: 'Entfernen', PROBLEM_UPDATING: 'Problem beim Aktualisieren', PROBLEM_LOADING: 'Problem beim Laden', - HTTP_ERROR: 'Error {0}', // TODO translate ANALOG_SENSOR: 'Analogsensor', ANALOG_SENSORS: 'Analogsensoren', SETTINGS: 'Einstellungen', diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index f355dfa83..83d123376 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -51,7 +51,6 @@ const en: Translation = { REMOVE: 'Remove', PROBLEM_UPDATING: 'Problem updating', PROBLEM_LOADING: 'Problem loading', - HTTP_ERROR: 'Error {0}', ANALOG_SENSOR: 'Analog Sensor', ANALOG_SENSORS: 'Analog Sensors', SETTINGS: 'Settings', diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index 08dbf762e..390407573 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -51,7 +51,6 @@ const fr: Translation = { REMOVE: 'Enlever', PROBLEM_UPDATING: 'Problème lors de la mise à jour', PROBLEM_LOADING: 'Problème lors du chargement', - HTTP_ERROR: 'Error {0}', // TODO translate ANALOG_SENSOR: 'Capteur analogique', ANALOG_SENSORS: 'Capteurs analogiques', SETTINGS: 'Paramètres', diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index d94ef323d..70920f937 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -51,7 +51,6 @@ const nl: Translation = { REMOVE: 'Verwijderen', PROBLEM_UPDATING: 'Probleem met updaten', PROBLEM_LOADING: 'Probleem met laden', - HTTP_ERROR: 'Error {0}', // TODO translate ANALOG_SENSOR: 'Analoge sensor', ANALOG_SENSORS: 'Analoge Sensoren', SETTINGS: 'Instellingen', diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index ae4a1eae8..21bc1346a 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -51,7 +51,6 @@ const no: Translation = { REMOVE: 'Fjern', PROBLEM_UPDATING: 'Problem med oppdatering', PROBLEM_LOADING: 'Problem med opplasting', - HTTP_ERROR: 'Error {0}', // TODO translate ANALOG_SENSOR: 'Analog Sensor', ANALOG_SENSORS: 'Analoge Sensorer', SETTINGS: 'Innstillinger', diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index 9ec0afc12..5d46e4c4b 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -51,7 +51,6 @@ const pl: BaseTranslation = { REMOVE: 'Usuń', PROBLEM_UPDATING: 'Problem z uaktualnieniem!', PROBLEM_LOADING: 'Problem z załadowaniem!', - HTTP_ERROR: 'Error {0}', // TODO translate ANALOG_SENSOR: '{{u|u||ustawienia u|ustawień u}}rządzeni{{a podłączonego do EMS-ESP|e||a podłączonego do EMS-ESP|a podłączonego do EMS-ESP}}', ANALOG_SENSORS: 'Urządzenia podłączone do EMS-ESP', SETTINGS: 'ustawienia', diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index 1c8de2a89..e34ddba92 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -51,7 +51,6 @@ const sv: Translation = { REMOVE: 'Ta bort', PROBLEM_UPDATING: 'Problem vid uppdatering', PROBLEM_LOADING: 'Problem vid hämtning', - HTTP_ERROR: 'Error {0}', // TODO translate ANALOG_SENSOR: 'Analog Sensor', ANALOG_SENSORS: 'Analoga Sensorer', SETTINGS: 'Inställningar', diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index c9ef87e1c..44a5dab43 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -51,7 +51,6 @@ const tr: Translation = { REMOVE: 'Kaldır', PROBLEM_UPDATING: 'Güncelleme Sorunu', PROBLEM_LOADING: 'Yükleme Sorunu', - HTTP_ERROR: 'Error {0}', // TODO translate ANALOG_SENSOR: 'Analog Sensör', ANALOG_SENSORS: 'Analog Sensörler', SETTINGS: 'Ayarlar', diff --git a/interface/src/project/DashboardDevices.tsx b/interface/src/project/DashboardDevices.tsx index 57b29a491..b1223a366 100644 --- a/interface/src/project/DashboardDevices.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -69,7 +69,7 @@ const DashboardDevices: FC = () => { immediate: true }); - const { data: deviceData, send: readDeviceData } = useRequest((id) => EMSESP.readDeviceData(id), { + const { data: deviceData, send: readDeviceData } = useRequest((data) => EMSESP.readDeviceData(data), { initialData: { data: [] }, @@ -77,12 +77,9 @@ const DashboardDevices: FC = () => { immediate: false }); - const { loading: submitting, send: writeDeviceValue } = useRequest( - (id: number, deviceValue: DeviceValue) => EMSESP.writeDeviceValue(id, deviceValue), - { - immediate: false - } - ); + const { loading: submitting, send: writeDeviceValue } = useRequest((data) => EMSESP.writeDeviceValue(data), { + immediate: false + }); useLayoutEffect(() => { function updateSize() { @@ -350,7 +347,7 @@ const DashboardDevices: FC = () => { toast.success(LL.WRITE_CMD_SENT()); }) .catch((error) => { - toast.error(LL.HTTP_ERROR(error)); + toast.error(error.message); }) .finally(async () => { setDeviceValueDialogOpen(false); diff --git a/interface/src/project/DashboardStatus.tsx b/interface/src/project/DashboardStatus.tsx index 887a142d8..9e3cd0c68 100644 --- a/interface/src/project/DashboardStatus.tsx +++ b/interface/src/project/DashboardStatus.tsx @@ -64,6 +64,8 @@ const showQuality = (stat: Stat) => { }; const DashboardStatus: FC = () => { + // TODO missing update! + const { loadData, data, errorMessage } = useRest({ read: EMSESP.readStatus }); const { LL } = useI18nContext(); diff --git a/interface/src/project/SettingsApplication.tsx b/interface/src/project/SettingsApplication.tsx index facc4a54b..8c871b5a7 100644 --- a/interface/src/project/SettingsApplication.tsx +++ b/interface/src/project/SettingsApplication.tsx @@ -23,7 +23,7 @@ import { import RestartMonitor from 'framework/system/RestartMonitor'; import { useI18nContext } from 'i18n/i18n-react'; -import { numberValue, extractErrorMessage, updateValueDirty, useRest } from 'utils'; +import { numberValue, extractErrorMessage, updateValueDirty, useRest2 } from 'utils'; import { validate } from 'validators'; export function boardProfileSelectItems() { @@ -47,7 +47,7 @@ const SettingsApplication: FC = () => { blocker, errorMessage, restartNeeded - } = useRest({ + } = useRest2({ read: EMSESP.readSettings, update: EMSESP.writeSettings }); @@ -60,6 +60,12 @@ const SettingsApplication: FC = () => { const [fieldErrors, setFieldErrors] = useState(); const [processingBoard, setProcessingBoard] = useState(false); + // TODO remove - just for testing loaddata + // useEffect(() => { + // void loadData(); + // }, []); + + // TODO replace with Alova! const updateBoardProfile = async (boardProfile: string) => { setProcessingBoard(true); try { diff --git a/interface/src/project/api.ts b/interface/src/project/api.ts index 289f73fb0..2b7515142 100644 --- a/interface/src/project/api.ts +++ b/interface/src/project/api.ts @@ -14,8 +14,7 @@ import type { WriteAnalogSensor, SensorData, Schedule, - Entities, - DeviceValue + Entities } from './types'; import type { AxiosPromise } from 'axios'; import { AXIOS, AXIOS_API, AXIOS_BIN, alovaInstance } from 'api/endpoints'; @@ -28,8 +27,11 @@ export const readDeviceData = (id: number) => responseType: 'arraybuffer' // uses msgpack }); -export const writeDeviceValue = (id: number, devicevalue: DeviceValue) => - alovaInstance.Post('/writeDeviceValue', { id, devicevalue }); +export const writeDeviceValue = (data: any) => alovaInstance.Post('/writeDeviceValue', data); + +// SettingsApplication +export const readSettings = () => alovaInstance.Get('/settings'); +export const writeSettings = (data: any) => alovaInstance.Post('/settings', data); // // TODO change below to use alova @@ -39,14 +41,6 @@ export function restart(): AxiosPromise { return AXIOS.post('/restart'); } -export function readSettings(): AxiosPromise { - return AXIOS.get('/settings'); -} - -export function writeSettings(settings: Settings): AxiosPromise { - return AXIOS.post('/settings', settings); // call command -} - // TODO change to GET export function getBoardProfile(boardProfile: BoardProfileName): AxiosPromise { return AXIOS.post('/boardProfile', boardProfile); diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index dbdcebcd5..5f80d7bd2 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -280,13 +280,6 @@ export interface APIcall { entity: string; id: any; } - -// TODO can be removed? -export interface WriteDeviceValue { - id: number; - devicevalue: DeviceValue; -} - export interface WriteAnalogSensor { id: number; gpio: number; diff --git a/interface/src/utils/binding.ts b/interface/src/utils/binding.ts index 3d5058f33..190584526 100644 --- a/interface/src/utils/binding.ts +++ b/interface/src/utils/binding.ts @@ -1,5 +1,3 @@ -type UpdateEntity = (state: (prevState: Readonly) => S) => void; - export const numberValue = (value: number) => (isNaN(value) ? '' : value.toString()); export const extractEventValue = (event: React.ChangeEvent) => { @@ -13,6 +11,8 @@ export const extractEventValue = (event: React.ChangeEvent) => } }; +type UpdateEntity = (state: (prevState: Readonly) => S) => void; + export const updateValue = (updateEntity: UpdateEntity) => (event: React.ChangeEvent) => { @@ -23,10 +23,13 @@ export const updateValue = }; export const updateValueDirty = - (origData: any, dirtyFlags: any, setDirtyFlags: any, updateEntity: UpdateEntity) => + (origData: any, dirtyFlags: any, setDirtyFlags: any, updateEntity: any) => + // (origData: any, dirtyFlags: any, setDirtyFlags: any, updateEntity: UpdateEntity) => (event: React.ChangeEvent) => { const updated_value = extractEventValue(event); const name = event.target.name; + + // TODO not sure how this is even working!! updateEntity((prevState) => ({ ...prevState, [name]: updated_value @@ -34,6 +37,11 @@ export const updateValueDirty = const arr: string[] = dirtyFlags; + // TODO remove comments + // console.log('updating ' + name + ' to ' + updated_value); // TODO remove + // console.log('dirtyFlags:', dirtyFlags); // TODO remove + // console.log('binding.ts origData:', origData); // TODO remove + if (origData[name] !== updated_value) { if (!arr.includes(name)) { arr.push(name); diff --git a/interface/src/utils/index.ts b/interface/src/utils/index.ts index db4bd056f..245acb6c7 100644 --- a/interface/src/utils/index.ts +++ b/interface/src/utils/index.ts @@ -4,4 +4,6 @@ export * from './route'; export * from './submit'; export * from './time'; export * from './useRest'; +// TODO remove +export * from './useRest2'; export * from './props'; diff --git a/interface/src/utils/useRest2.ts b/interface/src/utils/useRest2.ts new file mode 100644 index 000000000..29cae6065 --- /dev/null +++ b/interface/src/utils/useRest2.ts @@ -0,0 +1,92 @@ +import { useRequest, type Method } from 'alova'; +import { useState } from 'react'; +import { unstable_useBlocker as useBlocker } from 'react-router-dom'; +import { toast } from 'react-toastify'; + +import type { AlovaXHRRequestConfig, AlovaXHRResponse, AlovaXHRResponseHeaders } from '@alova/adapter-xhr'; + +import { useI18nContext } from 'i18n/i18n-react'; + +export interface RestRequestOptions { + read: () => Method, AlovaXHRResponseHeaders>; + update: ( + value: D + ) => Method, AlovaXHRResponseHeaders>; +} + +export const useRest2 = ({ read, update }: RestRequestOptions) => { + const { LL } = useI18nContext(); + + const [errorMessage, setErrorMessage] = useState(); + const [restartNeeded, setRestartNeeded] = useState(false); + const [origData, setOrigData] = useState(); + + const [dirtyFlags, setDirtyFlags] = useState([]); + const blocker = useBlocker(dirtyFlags.length !== 0); + + const { data: data, send: readData, update: updateData, onComplete: onReadComplete } = useRequest(read()); + + const { + loading: saving, + send: writeData, + onSuccess: onWriteSuccess + } = useRequest((newData: D) => update(newData), { immediate: false }); + + const setData = (new_data: D) => { + console.log('SET DATA'); // TODO remove console + console.log('new_data:', new_data); // TODO remove console + updateData({ + data: new_data + }); + }; + + onWriteSuccess(() => { + toast.success(LL.UPDATED_OF(LL.SETTINGS())); + setDirtyFlags([]); + }); + + onReadComplete((event) => { + setOrigData(event.data); // make a copy + }); + + const loadData = async () => { + setDirtyFlags([]); + setErrorMessage(undefined); + await readData().catch((error) => { + toast.error(error.message); + setErrorMessage(error.message); + }); + }; + + const saveData = async () => { + if (!data) { + return; + } + console.log('SAVE DATA'); // TODO remove console + setRestartNeeded(false); + setErrorMessage(undefined); + await writeData(data).catch((error) => { + if (error.message === 'Reboot required') { + setRestartNeeded(true); + } else { + toast.error(error.message); + setErrorMessage(error.message); + } + }); + }; + + return { + loadData, + saveData, + saving, + setData, + data, + origData, + dirtyFlags, + setDirtyFlags, + setOrigData, + blocker, + errorMessage, + restartNeeded + } as const; +}; diff --git a/interface/yarn.lock b/interface/yarn.lock index 02add5f85..c024a8686 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -1288,10 +1288,10 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^20.2.5": - version: 20.2.5 - resolution: "@types/node@npm:20.2.5" - checksum: 1c3db8a4ceb5e5d12e7cb140e37c14a16ce013084c6d65579b91cefbe0ecaca57d85093d968172b11c3d1d95bcbc5d972b08aa3dc3935206fb39bc6c10751102 +"@types/node@npm:^20.3.0": + version: 20.3.0 + resolution: "@types/node@npm:20.3.0" + checksum: f717d92c29c4877db394b604771b3734216f013312f93252f72c2018aabe8083be905fbcf0644c859938c8183b6e0245faaeaab94c9e78268b87a449bc6ef4aa languageName: node linkType: hard @@ -1368,14 +1368,14 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^18.2.9": - version: 18.2.9 - resolution: "@types/react@npm:18.2.9" +"@types/react@npm:^18.2.11": + version: 18.2.11 + resolution: "@types/react@npm:18.2.11" dependencies: "@types/prop-types": "*" "@types/scheduler": "*" csstype: ^3.0.2 - checksum: 5935a8cb8efc2804dbb0f85989970f654fb4117274b6d2836baa58d5e9dbfc09fd9d60e1f23a8d0fca70ad75e9f0e074790947899be40525cbe62f7ac58adcc6 + checksum: 9360d7be13195050eb16598796056123ee9d30470e7073a914300fed9282585d0dd0638bb5ff65843e308c3ac213d25b3388e8186f3134490c758f18f11f3bd8 languageName: node linkType: hard @@ -1536,8 +1536,8 @@ __metadata: "@mui/material": ^5.13.4 "@table-library/react-table-library": 4.1.4 "@types/lodash-es": ^4.17.7 - "@types/node": ^20.2.5 - "@types/react": ^18.2.9 + "@types/node": ^20.3.0 + "@types/react": ^18.2.11 "@types/react-dom": ^18.2.4 "@types/react-router-dom": ^5.3.3 "@typescript-eslint/eslint-plugin": ^5.59.9 @@ -1567,9 +1567,9 @@ __metadata: react-dom: latest react-dropzone: ^14.2.3 react-icons: ^4.9.0 - react-router-dom: ^6.12.0 + react-router-dom: ^6.12.1 react-toastify: ^9.1.3 - rollup-plugin-visualizer: ^5.9.0 + rollup-plugin-visualizer: ^5.9.2 sockette: ^2.0.6 terser: ^5.17.7 typesafe-i18n: ^5.24.3 @@ -4807,27 +4807,27 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^6.12.0": - version: 6.12.0 - resolution: "react-router-dom@npm:6.12.0" +"react-router-dom@npm:^6.12.1": + version: 6.12.1 + resolution: "react-router-dom@npm:6.12.1" dependencies: "@remix-run/router": 1.6.3 - react-router: 6.12.0 + react-router: 6.12.1 peerDependencies: react: ">=16.8" react-dom: ">=16.8" - checksum: 910b23ae3555e7baff97038c478b8fa65d5b54856c74e789c529850c002850a961329b692a8f1f4643100f46075918d2b55cbf3c7f87f85588387148010600f5 + checksum: 4de0d4159a9dd2de0477d7608e9055262ebdd5dc41fc918b44b38170cc8ed407fa7dbb73bdb85e9469614502ad4772523b8a7f32c2609e62973feb41b70d871b languageName: node linkType: hard -"react-router@npm:6.12.0": - version: 6.12.0 - resolution: "react-router@npm:6.12.0" +"react-router@npm:6.12.1": + version: 6.12.1 + resolution: "react-router@npm:6.12.1" dependencies: "@remix-run/router": 1.6.3 peerDependencies: react: ">=16.8" - checksum: b7cee04f5edbf48f1aa0303107514a3a9dc2b6f94a7a753fbf4eb8dde1d3eb67940204ff0b8b907c5b09f69c716dd958a6187346b1c35ccf19162d74e77d99af + checksum: 33a39eca122f3519f1aa91d8e6585fab669b3b06b621aa624f7db1ffda6b84676facd57028652f2fba7325a208abef906a5ba6c91d55d5a5d29993583d82dd61 languageName: node linkType: hard @@ -5030,9 +5030,9 @@ __metadata: languageName: node linkType: hard -"rollup-plugin-visualizer@npm:^5.9.0": - version: 5.9.0 - resolution: "rollup-plugin-visualizer@npm:5.9.0" +"rollup-plugin-visualizer@npm:^5.9.2": + version: 5.9.2 + resolution: "rollup-plugin-visualizer@npm:5.9.2" dependencies: open: ^8.4.0 picomatch: ^2.3.1 @@ -5045,7 +5045,7 @@ __metadata: optional: true bin: rollup-plugin-visualizer: dist/bin/cli.js - checksum: dc706e09c78124b2e05b58779c757e488c76e679b92795f8538f8efffa61845339d75a7f767d5a9eeb5d7e4fd76a835dd93ebf17315bf8a0a0e5fc85a4f59ab5 + checksum: e6280bed797084e9f9ee06726e46706e32854fc7647c5c5bcec68a9be8ca8e6fbf7f8a0b2f76255e9df72e84135a7d57eb2662b6cfd68496a1ef60fa33597eaf languageName: node linkType: hard diff --git a/mock-api/server.js b/mock-api/server.js index 72159d075..04ede86d1 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -2128,8 +2128,8 @@ rest_server.get(EMSESP_SETTINGS_ENDPOINT, (req, res) => { rest_server.post(EMSESP_SETTINGS_ENDPOINT, (req, res) => { settings = req.body; console.log('Write settings: ' + JSON.stringify(settings)); - res.status(202).json(settings); // restart needed - // res.status(200).json(settings); // no restart needed + // res.status(202).json(settings); // restart needed + res.status(200).json(settings); // no restart needed }); rest_server.get(EMSESP_CORE_DATA_ENDPOINT, (req, res) => { console.log('send back core data...'); @@ -2337,6 +2337,7 @@ rest_server.post(EMSESP_WRITE_ENTITIES_ENDPOINT, (req, res) => { }); rest_server.post(EMSESP_WRITE_VALUE_ENDPOINT, async (req, res) => { + console.log(req.body); const devicevalue = req.body.devicevalue; const id = req.body.id; console.log('Write device value for id : ' + id); diff --git a/src/version.h b/src/version.h index f49ccbca4..6243a56a3 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.6.0-dev.12" +#define EMSESP_APP_VERSION "3.6.0-dev.13" diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index c44241a33..85e6a0353 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -23,20 +23,17 @@ namespace emsesp { using namespace std::placeholders; // for `_1` etc WebDataService::WebDataService(AsyncWebServer * server, SecurityManager * securityManager) - : _device_data_handler(DEVICE_DATA_SERVICE_PATH, - securityManager->wrapCallback(std::bind(&WebDataService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)) - , _write_value_handler(WRITE_DEVICE_VALUE_SERVICE_PATH, + : _write_value_handler(WRITE_DEVICE_VALUE_SERVICE_PATH, securityManager->wrapCallback(std::bind(&WebDataService::write_device_value, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) , _write_temperature_handler(WRITE_TEMPERATURE_SENSOR_SERVICE_PATH, securityManager->wrapCallback(std::bind(&WebDataService::write_temperature_sensor, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) , _write_analog_handler(WRITE_ANALOG_SENSOR_SERVICE_PATH, securityManager->wrapCallback(std::bind(&WebDataService::write_analog_sensor, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) { - // TODO Get for /deviceData + // GET's server->on(DEVICE_DATA_SERVICE_PATH, HTTP_GET, - securityManager->wrapRequest(std::bind(&WebDataService::device_data2, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); - + securityManager->wrapRequest(std::bind(&WebDataService::device_data, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); server->on(CORE_DATA_SERVICE_PATH, HTTP_GET, @@ -46,14 +43,11 @@ WebDataService::WebDataService(AsyncWebServer * server, SecurityManager * securi HTTP_GET, securityManager->wrapRequest(std::bind(&WebDataService::sensor_data, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); + // POST's server->on(SCAN_DEVICES_SERVICE_PATH, HTTP_POST, securityManager->wrapRequest(std::bind(&WebDataService::scan_devices, this, _1), AuthenticationPredicates::IS_ADMIN)); - _device_data_handler.setMethod(HTTP_POST); - _device_data_handler.setMaxContentLength(256); - server->addHandler(&_device_data_handler); - _write_value_handler.setMethod(HTTP_POST); _write_value_handler.setMaxContentLength(256); server->addHandler(&_write_value_handler); @@ -175,11 +169,10 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) { // The unique_id is the unique record ID from the Web table to identify which device to load // Compresses the JSON using MsgPack https://msgpack.org/index.html -void WebDataService::device_data2(AsyncWebServerRequest * request) { +void WebDataService::device_data(AsyncWebServerRequest * request) { uint8_t id; if (request->hasParam(F_(id))) { - // TODO get id - id = Helpers::atoint(request->getParam(F_(id))->value().c_str()); + id = Helpers::atoint(request->getParam(F_(id))->value().c_str()); // get id from url size_t buffer = EMSESP_JSON_SIZE_XXXXLARGE; auto * response = new MsgpackAsyncJsonResponse(false, buffer); @@ -230,56 +223,6 @@ void WebDataService::device_data2(AsyncWebServerRequest * request) { request->send(response); } -// The unique_id is the unique record ID from the Web table to identify which device to load -// Compresses the JSON using MsgPack https://msgpack.org/index.html -void WebDataService::device_data(AsyncWebServerRequest * request, JsonVariant & json) { - if (json.is()) { - size_t buffer = EMSESP_JSON_SIZE_XXXXLARGE; - auto * response = new MsgpackAsyncJsonResponse(false, buffer); - while (!response->getSize()) { - delete response; - buffer -= 1024; - response = new MsgpackAsyncJsonResponse(false, buffer); - } - for (const auto & emsdevice : EMSESP::emsdevices) { - if (emsdevice->unique_id() == json["id"]) { - // wait max 2.5 sec for updated data (post_send_delay is 2 sec) - for (uint16_t i = 0; i < (emsesp::TxService::POST_SEND_DELAY + 500) && EMSESP::wait_validate(); i++) { - delay(1); - } - EMSESP::wait_validate(0); // reset in case of timeout -#ifndef EMSESP_STANDALONE - JsonObject output = response->getRoot(); - emsdevice->generate_values_web(output); -#endif - -#if defined(EMSESP_DEBUG) - size_t length = response->setLength(); - EMSESP::logger().debug("Dashboard buffer used: %d", length); -#else - response->setLength(); -#endif - request->send(response); - return; - } - } -#ifndef EMSESP_STANDALONE - if (json["id"] == 99) { - JsonObject output = response->getRoot(); - EMSESP::webEntityService.generate_value_web(output); - response->setLength(); - request->send(response); - return; - } -#endif - } - - // invalid but send ok - AsyncWebServerResponse * response = request->beginResponse(200); - request->send(response); -} - -// takes a command and its data value from a specific EMS Device, from the Web // assumes the service has been checked for admin authentication void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVariant & json) { if (json.is()) { diff --git a/src/web/WebDataService.h b/src/web/WebDataService.h index 3c7439483..d85d20988 100644 --- a/src/web/WebDataService.h +++ b/src/web/WebDataService.h @@ -21,7 +21,6 @@ // GET #define CORE_DATA_SERVICE_PATH "/rest/coreData" -#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices" #define DEVICE_DATA_SERVICE_PATH "/rest/deviceData" #define SENSOR_DATA_SERVICE_PATH "/rest/sensorData" @@ -29,6 +28,7 @@ #define WRITE_DEVICE_VALUE_SERVICE_PATH "/rest/writeDeviceValue" #define WRITE_TEMPERATURE_SENSOR_SERVICE_PATH "/rest/writeTemperatureSensor" #define WRITE_ANALOG_SENSOR_SERVICE_PATH "/rest/writeAnalogSensor" +#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices" namespace emsesp { @@ -44,22 +44,15 @@ class WebDataService { // GET void core_data(AsyncWebServerRequest * request); void sensor_data(AsyncWebServerRequest * request); - - // TODO make it a Get - void device_data2(AsyncWebServerRequest * request); - + void device_data(AsyncWebServerRequest * request); // POST - // TODO probably can be removed - void device_data(AsyncWebServerRequest * request, JsonVariant & json); - - void write_device_value(AsyncWebServerRequest * request, JsonVariant & json); void write_temperature_sensor(AsyncWebServerRequest * request, JsonVariant & json); void write_analog_sensor(AsyncWebServerRequest * request, JsonVariant & json); - void scan_devices(AsyncWebServerRequest * request); + void scan_devices(AsyncWebServerRequest * request); // command - AsyncCallbackJsonWebHandler _device_data_handler, _write_value_handler, _write_temperature_handler, _write_analog_handler; + AsyncCallbackJsonWebHandler _write_value_handler, _write_temperature_handler, _write_analog_handler; }; } // namespace emsesp