From 2e63d602732b2c87acb06704ee58069a6cae3468 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 4 Jun 2023 15:22:09 +0200 Subject: [PATCH 01/30] alova implementation, testing --- interface/.env.development | 2 + interface/package.json | 4 +- interface/public/css/roboto.css | 2 +- interface/src/api/endpoints.ts | 60 +++++++- interface/src/api/unpack.ts | 3 - interface/src/project/DashboardDevices.tsx | 135 ++++++++++++------ .../src/project/DashboardDevicesDialog.tsx | 32 ++++- interface/src/project/api.ts | 45 +++--- interface/src/project/types.ts | 2 + interface/yarn.lock | 116 ++++++++------- mock-api/server.js | 23 ++- 11 files changed, 280 insertions(+), 144 deletions(-) create mode 100644 interface/.env.development diff --git a/interface/.env.development b/interface/.env.development new file mode 100644 index 000000000..19cc804fe --- /dev/null +++ b/interface/.env.development @@ -0,0 +1,2 @@ +VITE_ALOVA_TIPS=0 +REACT_APP_ALOVA_TIPS=0 \ No newline at end of file diff --git a/interface/package.json b/interface/package.json index 0898b2c16..ab3303400 100644 --- a/interface/package.json +++ b/interface/package.json @@ -19,6 +19,7 @@ "lint": "eslint . --cache --fix" }, "dependencies": { + "@alova/adapter-xhr": "^1.0.0", "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.11.16", @@ -29,6 +30,7 @@ "@types/react": "^18.2.8", "@types/react-dom": "^18.2.4", "@types/react-router-dom": "^5.3.3", + "alova": "^2.5.4", "async-validator": "^4.2.5", "axios": "^1.4.0", "history": "^5.3.0", @@ -47,7 +49,7 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.59.8", "@typescript-eslint/parser": "^5.59.8", - "@vitejs/plugin-react-swc": "^3.3.1", + "@vitejs/plugin-react-swc": "^3.3.2", "eslint": "^8.42.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.0.0", diff --git a/interface/public/css/roboto.css b/interface/public/css/roboto.css index dd5649d0c..0c05736a7 100644 --- a/interface/public/css/roboto.css +++ b/interface/public/css/roboto.css @@ -8,7 +8,7 @@ font-style: normal; font-weight: 400; /* src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxK.woff2) format('woff2'); */ - src: local('Roboto'), local('Roboto-Regular'), url(../fonts/re.off2) format('woff2'); + src: local('Roboto'), local('Roboto-Regular'), url(../fonts/re.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0104-0107, U+0118-0119, U+011E-011F, U+0130-0131, U+0141-0144, U+0152-0153, U+015A-015B, U+015E-015F, U+0179-017C, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index 7c003693b..19ce5b290 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -1,12 +1,18 @@ +import { xhrRequestAdapter } from '@alova/adapter-xhr'; +import { createAlova, useRequest } from 'alova'; +import GlobalFetch from 'alova/GlobalFetch'; +import ReactHook from 'alova/react'; import axios from 'axios'; import { unpack } from './unpack'; import type { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios'; export const WS_BASE_URL = '/ws/'; -export const API_BASE_URL = '/rest/'; export const ES_BASE_URL = '/es/'; -export const EMSESP_API_BASE_URL = '/api/'; + +export const REST_BASE_URL = '/rest/'; +export const API_BASE_URL = '/api/'; + export const ACCESS_TOKEN = 'access_token'; const location = window.location; @@ -14,8 +20,47 @@ const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; export const WEB_SOCKET_ROOT = webProtocol + '//' + location.host + WS_BASE_URL; export const EVENT_SOURCE_ROOT = location.protocol + '//' + location.host + ES_BASE_URL; +export const alovaInstance = createAlova({ + baseURL: '/rest/', + statesHook: ReactHook, + 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); + } + }, + responsed: (response) => response.data + + // TODO add error handling for Push? + + // return JSON.stringify(response.data); + + // responded: { + // // When using the GlobalFetch request adapter, the first parameter receives the Response object + // // The second parameter is the method instance of the current request, you can use it to synchronize the configuration information before and after the request + // onSuccess: async (response, method) => { + // if (response.status >= 400) { + // throw new Error(response.statusText); + // } + // console.log('response', response); + // const json = await response.json(); + // // The parsed response data will be passed to the transformData hook function of the method instance, and these functions will be explained later + // return json; + // }, + + // // Interceptor for request failure + // // This interceptor will be entered when the request is wrong. + // // The second parameter is the method instance of the current request, you can use it to synchronize the configuration information before and after the request + // onError: (error, method) => { + // alert(error.message); + // } + // } +}); + export const AXIOS = axios.create({ - baseURL: API_BASE_URL, + baseURL: REST_BASE_URL, headers: { 'Content-Type': 'application/json' }, @@ -35,7 +80,7 @@ export const AXIOS = axios.create({ }); export const AXIOS_API = axios.create({ - baseURL: EMSESP_API_BASE_URL, + baseURL: API_BASE_URL, headers: { 'Content-Type': 'application/json' }, @@ -55,7 +100,7 @@ export const AXIOS_API = axios.create({ }); export const AXIOS_BIN = axios.create({ - baseURL: API_BASE_URL, + baseURL: REST_BASE_URL, headers: { 'Content-Type': 'application/json' }, @@ -73,10 +118,11 @@ export const AXIOS_BIN = axios.create({ return JSON.stringify(data); } ], - // transformResponse: [(data) => decode(data)] - transformResponse: [(data) => unpack(data)] // new using msgpackr + transformResponse: [(data) => unpack(data)] }); +// TODO replace with alova +// TODO see https://alova.js.org/next-step/download-upload-progress export interface FileUploadConfig { cancelToken?: CancelToken; onUploadProgress?: (progressEvent: AxiosProgressEvent) => void; diff --git a/interface/src/api/unpack.ts b/interface/src/api/unpack.ts index d28619786..a25078d5d 100644 --- a/interface/src/api/unpack.ts +++ b/interface/src/api/unpack.ts @@ -968,7 +968,6 @@ currentExtensions[0x69] = (data) => { if (!referenceMap) referenceMap = new Map(); const token = src[position]; let target; - // TODO: handle Maps, Sets, and other types that can cycle; this is complicated, because you potentially need to read // ahead past references to record structure definitions if ((token >= 0x90 && token < 0xa0) || token == 0xdc || token == 0xdd) target = []; else target = {}; @@ -1041,7 +1040,6 @@ currentExtensions[0xff] = (data) => { ((data[3] & 0x3) * 0x100000000 + data[4] * 0x1000000 + (data[5] << 16) + (data[6] << 8) + data[7]) * 1000 ); else if (data.length == 12) - // TODO: Implement support for negative return new Date( ((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 + ((data[4] & 0x80 ? -0x1000000000000 : 0) + @@ -1070,7 +1068,6 @@ function saveState(callback) { const savedReferenceMap = referenceMap; const savedBundledStrings = bundledStrings; - // TODO: We may need to revisit this if we do more external calls to user code (since it could be slow) const savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed const savedStructures = currentStructures; const savedStructuresContents = currentStructures.slice(0, currentStructures.length); diff --git a/interface/src/project/DashboardDevices.tsx b/interface/src/project/DashboardDevices.tsx index b49af0d35..1b7dd2013 100644 --- a/interface/src/project/DashboardDevices.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -30,6 +30,7 @@ import { useRowSelect } from '@table-library/react-table-library/select'; import { useSort, SortToggleType } from '@table-library/react-table-library/sort'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; +import { useRequest } from 'alova'; import { useState, useContext, useEffect, useCallback, useLayoutEffect } from 'react'; import { IconContext } from 'react-icons'; @@ -54,17 +55,45 @@ const DashboardDevices: FC = () => { const [size, setSize] = useState([0, 0]); const { me } = useContext(AuthenticatedContext); const { LL } = useI18nContext(); - const [deviceData, setDeviceData] = useState({ data: [] }); const [selectedDeviceValue, setSelectedDeviceValue] = useState(); const [onlyFav, setOnlyFav] = useState(false); const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false); const [showDeviceInfo, setShowDeviceInfo] = useState(false); const [selectedDevice, setSelectedDevice] = useState(); - const [coreData, setCoreData] = useState({ - connected: true, - devices: [] + + // TODO remove + // const [deviceData, setDeviceData] = useState({ data: [] }); + // const [coreData, setCoreData] = useState({ + // connected: true, + // devices: [] + // }); + + const { data: coreData, send: readCoreData } = useRequest(() => EMSESP.readCoreData(), { + initialData: { + connected: true, + devices: [] + }, + force: true, + immediate: false }); + // TODO prevent firing when page is loaded + const { data: deviceData, send: readDeviceData } = useRequest((id) => EMSESP.readDeviceData(id), { + initialData: { + data: [] + }, + force: true, + immediate: false + }); + + // TODO prevent firing when page is loaded + const { loading: submitting, send: writeDeviceValue } = useRequest( + (id: number, deviceValue: DeviceValue) => EMSESP.writeDeviceValue(id, deviceValue), + { + immediate: false + } + ); + useLayoutEffect(() => { function updateSize() { setSize([window.innerWidth, window.innerHeight]); @@ -212,19 +241,21 @@ const DashboardDevices: FC = () => { } ); - const fetchDeviceData = async (id: number) => { - try { - setDeviceData((await EMSESP.readDeviceData({ id })).data); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } - }; + // TODO remove + // const fetchDeviceData = async (id: number) => { + // try { + // setDeviceData((await EMSESP.readDeviceData({ id })).data); + // } catch (error) { + // toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); + // } + // }; - function onSelectChange(action: any, state: any) { - setDeviceData({ data: [] }); + async function onSelectChange(action: any, state: any) { + // TODO check if still needed + // setDeviceData({ data: [] }); setSelectedDevice(state.id); if (action.type === 'ADD_BY_ID_EXCLUSIVELY') { - void fetchDeviceData(state.id); + await readDeviceData(state.id); } } @@ -257,27 +288,29 @@ const DashboardDevices: FC = () => { }; }, [escFunction]); - const fetchCoreData = useCallback(async () => { - try { - setSelectedDevice(undefined); - setCoreData((await EMSESP.readCoreData()).data); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } - }, [LL]); + // TODO remove + // const fetchCoreData = useCallback(async () => { + // try { + // setSelectedDevice(undefined); + // setCoreData((await EMSESP.readCoreData()).data); + // } catch (error) { + // toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); + // } + // }, [LL]); - useEffect(() => { - void fetchCoreData(); - }, [fetchCoreData]); + // TODO remove + // useEffect(() => { + // void fetchCoreData2(); + // }, [fetchCoreData2]); const refreshData = () => { if (deviceValueDialogOpen) { return; } if (selectedDevice) { - void fetchDeviceData(selectedDevice); + void readDeviceData(selectedDevice); } else { - void fetchCoreData(); + void readCoreData(); } }; @@ -348,25 +381,32 @@ const DashboardDevices: FC = () => { const deviceValueDialogSave = async (dv: DeviceValue) => { const selectedDeviceID = Number(device_select.state.id); - try { - const response = await EMSESP.writeDeviceValue({ - id: selectedDeviceID, - devicevalue: dv - }); - if (response.status === 204) { - toast.error(LL.WRITE_CMD_FAILED()); - } else if (response.status === 403) { - toast.error(LL.ACCESS_DENIED()); - } else { - toast.success(LL.WRITE_CMD_SENT()); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } finally { - setDeviceValueDialogOpen(false); - await fetchDeviceData(selectedDeviceID); - setSelectedDeviceValue(undefined); - } + // TODO For all Push, do error handling? + const response = await writeDeviceValue(selectedDeviceID, dv); + console.log(response); + setDeviceValueDialogOpen(false); + await readDeviceData(selectedDeviceID); + setSelectedDeviceValue(undefined); + + // try { + // const response = await EMSESP.writeDeviceValue({ + // id: selectedDeviceID, + // devicevalue: dv + // }); + // if (response.status === 204) { + // toast.error(LL.WRITE_CMD_FAILED()); + // } else if (response.status === 403) { + // toast.error(LL.ACCESS_DENIED()); + // } else { + // toast.success(LL.WRITE_CMD_SENT()); + // } + // } catch (error) { + // toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); + // } finally { + // setDeviceValueDialogOpen(false); + // await readDeviceData(selectedDeviceID); + // setSelectedDeviceValue(undefined); + // } }; const renderDeviceDetails = () => { @@ -457,7 +497,7 @@ const DashboardDevices: FC = () => { }; const renderDeviceData = () => { - if (!selectedDevice) { + if (!selectedDevice || deviceData.data === undefined) { return; } @@ -612,6 +652,7 @@ const DashboardDevices: FC = () => { !hasMask(selectedDeviceValue.id, DeviceEntityMask.DV_READONLY) } validator={deviceValueItemValidation(selectedDeviceValue)} + progress={submitting} /> )} diff --git a/interface/src/project/DashboardDevicesDialog.tsx b/interface/src/project/DashboardDevicesDialog.tsx index f2ff2bc33..bc725f0a6 100644 --- a/interface/src/project/DashboardDevicesDialog.tsx +++ b/interface/src/project/DashboardDevicesDialog.tsx @@ -13,8 +13,10 @@ import { FormHelperText, Grid, Box, - Typography + Typography, + CircularProgress } from '@mui/material'; +import { green } from '@mui/material/colors'; import { useState, useEffect } from 'react'; import { DeviceValueUOM, DeviceValueUOM_s } from './types'; @@ -22,7 +24,7 @@ import type { DeviceValue } from './types'; import type Schema from 'async-validator'; import type { ValidateFieldsError } from 'async-validator'; -import { ValidatedTextField } from 'components'; +import { ButtonRow, ValidatedTextField } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import { updateValue } from 'utils'; @@ -35,6 +37,7 @@ type DashboardDevicesDialogProps = { selectedItem: DeviceValue; writeable: boolean; validator: Schema; + progress: boolean; }; const DashboarDevicesDialog = ({ @@ -43,7 +46,8 @@ const DashboarDevicesDialog = ({ onSave, selectedItem, writeable, - validator + validator, + progress }: DashboardDevicesDialogProps) => { const { LL } = useI18nContext(); const [editItem, setEditItem] = useState(selectedItem); @@ -184,14 +188,32 @@ const DashboarDevicesDialog = ({ {writeable ? ( - <> + - + {progress && ( + + )} + ) : ( diff --git a/interface/src/project/api.ts b/interface/src/project/api.ts index 2b7515142..b925027c1 100644 --- a/interface/src/project/api.ts +++ b/interface/src/project/api.ts @@ -1,86 +1,63 @@ import type { - BoardProfile, - BoardProfileName, APIcall, Settings, Status, CoreData, Devices, - DeviceData, DeviceEntity, - UniqueID, - CustomEntities, WriteTemperatureSensor, WriteAnalogSensor, SensorData, Schedule, - Entities + Entities, + DeviceData } from './types'; import type { AxiosPromise } from 'axios'; -import { AXIOS, AXIOS_API, AXIOS_BIN, alovaInstance } from 'api/endpoints'; +import { AXIOS, AXIOS_API, alovaInstance } from 'api/endpoints'; +// DashboardDevices export const readCoreData = () => alovaInstance.Get(`/coreData`); - export const readDeviceData = (id: number) => alovaInstance.Get('/deviceData', { params: { id }, responseType: 'arraybuffer' // uses msgpack }); - 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); +export const getBoardProfile = (boardProfile: string) => + alovaInstance.Get('/boardProfile', { + params: { boardProfile } + }); +export const restart = () => alovaInstance.Post('/restart'); -// -// TODO change below to use alova -// +// SettingsCustomization +export const readDeviceEntities = (id: number) => + alovaInstance.Get('/deviceEntities', { + params: { id }, + responseType: 'arraybuffer', + transformData(rawData: any) { + return rawData.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })); + } + }); +export const readDevices = () => alovaInstance.Get('/devices'); +export const resetCustomizations = () => alovaInstance.Post('/resetCustomizations'); +export const writeCustomEntities = (data: any) => alovaInstance.Post('/customEntities', data); -export function restart(): AxiosPromise { - return AXIOS.post('/restart'); -} +// DashboardSensors +export const readSensorData = () => alovaInstance.Get('/sensorData'); +export const writeTemperatureSensor = (ts: WriteTemperatureSensor) => alovaInstance.Post('/writeTemperatureSensor', ts); +export const writeAnalogSensor = (as: WriteAnalogSensor) => alovaInstance.Post('/writeAnalogSensor', as); -// TODO change to GET -export function getBoardProfile(boardProfile: BoardProfileName): AxiosPromise { - return AXIOS.post('/boardProfile', boardProfile); -} -// TODO change to GET -export function readDeviceEntities(unique_id: UniqueID): AxiosPromise { - return AXIOS_BIN.post('/deviceEntities', unique_id); -} +// TODO think about naming, get... and not get etc... -export function readStatus(): AxiosPromise { - return AXIOS.get('/status'); -} +// DashboardStatus +export const readStatus = () => alovaInstance.Get('/status'); +export const scanDevices = () => alovaInstance.Post('/scanDevices'); -export function readDevices(): AxiosPromise { - return AXIOS.get('/devices'); -} - -export function scanDevices(): AxiosPromise { - return AXIOS.post('/scanDevices'); // call command -} - -export function readSensorData(): AxiosPromise { - return AXIOS.get('/sensorData'); -} - -export function writeCustomEntities(customEntities: CustomEntities): AxiosPromise { - return AXIOS.post('/customEntities', customEntities); -} - -export function writeTemperatureSensor(ts: WriteTemperatureSensor): AxiosPromise { - return AXIOS.post('/writeTemperatureSensor', ts); -} - -export function writeAnalogSensor(as: WriteAnalogSensor): AxiosPromise { - return AXIOS.post('/writeAnalogSensor', as); -} - -export function resetCustomizations(): AxiosPromise { - return AXIOS.post('/resetCustomizations'); // command -} +// ALOVA goes here.... export function API(apiCall: APIcall): AxiosPromise { return AXIOS_API.post('/', apiCall); // command diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 5f80d7bd2..a50b0281c 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -131,8 +131,6 @@ export interface DeviceValue { m?: number; // min, optional x?: number; // max, optional } - -// TODO can be refacvtored to DeviceValue[]? export interface DeviceData { data: DeviceValue[]; } @@ -152,16 +150,6 @@ export interface DeviceEntity { o_ma?: number; // original max value } -export interface CustomEntities { - id: number; - entity_ids: string[]; -} - -// TODO can be removed? -export interface UniqueID { - id: number; -} - export enum DeviceValueUOM { NONE = 0, DEGREES, @@ -258,10 +246,6 @@ export const BOARD_PROFILES: BoardProfiles = { S3MINI: 'Liligo S3' }; -export interface BoardProfileName { - board_profile: string; -} - export interface BoardProfile { board_profile: string; led_gpio: number; diff --git a/interface/src/utils/binding.ts b/interface/src/utils/binding.ts index 190584526..74a755a52 100644 --- a/interface/src/utils/binding.ts +++ b/interface/src/utils/binding.ts @@ -23,14 +23,13 @@ export const updateValue = }; export const updateValueDirty = - (origData: any, dirtyFlags: any, setDirtyFlags: any, updateEntity: any) => - // (origData: any, dirtyFlags: any, setDirtyFlags: any, updateEntity: UpdateEntity) => + (origData: any, dirtyFlags: any, setDirtyFlags: any, updateDataValue: any) => (event: React.ChangeEvent) => { const updated_value = extractEventValue(event); const name = event.target.name; // TODO not sure how this is even working!! - updateEntity((prevState) => ({ + updateDataValue((prevState) => ({ ...prevState, [name]: updated_value })); @@ -38,9 +37,9 @@ 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 + // console.log('updating ' + name + ' to ' + updated_value); + // console.log('dirtyFlags:', dirtyFlags); + // console.log('binding.ts origData:', origData); if (origData[name] !== updated_value) { if (!arr.includes(name)) { diff --git a/interface/src/utils/endpoints.ts b/interface/src/utils/endpoints.ts index 98b9c55ac..d0de377fe 100644 --- a/interface/src/utils/endpoints.ts +++ b/interface/src/utils/endpoints.ts @@ -1,4 +1,4 @@ -// TODO can be removed! +// TODO extractErrorMessage function can be removed! export const extractErrorMessage = (error: any, defaultMessage: string) => { if (error.request) { return defaultMessage + ' (' + error.request.status + ': ' + error.request.statusText + ')'; diff --git a/interface/src/utils/index.ts b/interface/src/utils/index.ts index 245acb6c7..c17d6949b 100644 --- a/interface/src/utils/index.ts +++ b/interface/src/utils/index.ts @@ -4,6 +4,6 @@ export * from './route'; export * from './submit'; export * from './time'; export * from './useRest'; -// TODO remove +// TODO remove useRest2 export * from './useRest2'; export * from './props'; diff --git a/interface/src/utils/useRest.ts b/interface/src/utils/useRest.ts index 9a5378802..1d71f1a07 100644 --- a/interface/src/utils/useRest.ts +++ b/interface/src/utils/useRest.ts @@ -52,8 +52,8 @@ export const useRest = ({ read, update }: RestRequestOptions) => { const response = await update(toSave); setOrigData(response.data); setData(response.data); - if (response.status === 202) { - setRestartNeeded(true); + if (response.status === 205) { + setRestartNeeded(true); // reboot required } else { toast.success(LL.UPDATED_OF(LL.SETTINGS())); } diff --git a/interface/src/utils/useRest2.ts b/interface/src/utils/useRest2.ts index 29cae6065..7e8688da0 100644 --- a/interface/src/utils/useRest2.ts +++ b/interface/src/utils/useRest2.ts @@ -7,14 +7,15 @@ import type { AlovaXHRRequestConfig, AlovaXHRResponse, AlovaXHRResponseHeaders } import { useI18nContext } from 'i18n/i18n-react'; -export interface RestRequestOptions { +export interface RestRequestOptions2 { read: () => Method, AlovaXHRResponseHeaders>; update: ( value: D ) => Method, AlovaXHRResponseHeaders>; } -export const useRest2 = ({ read, update }: RestRequestOptions) => { +// TODO rename back to useRest +export const useRest2 = ({ read, update }: RestRequestOptions2) => { const { LL } = useI18nContext(); const [errorMessage, setErrorMessage] = useState(); @@ -32,12 +33,8 @@ export const useRest2 = ({ read, update }: RestRequestOptions) => { 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 - }); + const updateDataValue = (new_data: D) => { + updateData({ data: new_data }); }; onWriteSuccess(() => { @@ -46,7 +43,7 @@ export const useRest2 = ({ read, update }: RestRequestOptions) => { }); onReadComplete((event) => { - setOrigData(event.data); // make a copy + setOrigData(event.data); }); const loadData = async () => { @@ -62,7 +59,6 @@ export const useRest2 = ({ read, update }: RestRequestOptions) => { if (!data) { return; } - console.log('SAVE DATA'); // TODO remove console setRestartNeeded(false); setErrorMessage(undefined); await writeData(data).catch((error) => { @@ -79,7 +75,7 @@ export const useRest2 = ({ read, update }: RestRequestOptions) => { loadData, saveData, saving, - setData, + updateDataValue, data, origData, dirtyFlags, diff --git a/interface/yarn.lock b/interface/yarn.lock index c024a8686..ca56b2b5d 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -767,14 +767,14 @@ __metadata: languageName: node linkType: hard -"@mui/material@npm:^5.13.4": - version: 5.13.4 - resolution: "@mui/material@npm:5.13.4" +"@mui/material@npm:^5.13.5": + version: 5.13.5 + resolution: "@mui/material@npm:5.13.5" dependencies: "@babel/runtime": ^7.21.0 "@mui/base": 5.0.0-beta.4 "@mui/core-downloads-tracker": ^5.13.4 - "@mui/system": ^5.13.2 + "@mui/system": ^5.13.5 "@mui/types": ^7.2.4 "@mui/utils": ^5.13.1 "@types/react-transition-group": ^4.4.6 @@ -796,7 +796,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: 1f0b26c74e06fb6849af7398b8bc1d211d0af146822c998a9c55c31552ffcf426834eac56dc909c15bae8a3b957df91f32d9c1b6cb1300e063d184bfb530512f + checksum: 325a99809efa041aa615b144bfde634ad7def22102b6267769e140aee0cdeaa3f611b79d4e3dc85a832b1f120da19b3e57933fb17487c7d5f67d7c2bbe7f3254 languageName: node linkType: hard @@ -838,9 +838,9 @@ __metadata: languageName: node linkType: hard -"@mui/system@npm:^5.13.2": - version: 5.13.2 - resolution: "@mui/system@npm:5.13.2" +"@mui/system@npm:^5.13.5": + version: 5.13.5 + resolution: "@mui/system@npm:5.13.5" dependencies: "@babel/runtime": ^7.21.0 "@mui/private-theming": ^5.13.1 @@ -862,7 +862,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: 34ebb580e5dd83123cc397c3fd54c3430f66ab715eb1538cf2510821d88249814294f79ea046081b61249643383fd9c23552d9791322855fa2099bf8f1c4e51b + checksum: 0bef4c575d9c54e7d93ad14009aecf4a0f18440398a14a6523f3fcea665913ceb344dc496d02b56a3ef53e4dac828f8e7ca5a55fb60448a76363622159d18379 languageName: node linkType: hard @@ -1288,10 +1288,10 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^20.3.0": - version: 20.3.0 - resolution: "@types/node@npm:20.3.0" - checksum: f717d92c29c4877db394b604771b3734216f013312f93252f72c2018aabe8083be905fbcf0644c859938c8183b6e0245faaeaab94c9e78268b87a449bc6ef4aa +"@types/node@npm:^20.3.1": + version: 20.3.1 + resolution: "@types/node@npm:20.3.1" + checksum: 7e8a6f5d6fc1ad3778f038f5f8df570741459984280fd2e9539af32620d93438c955fd1b90d00f9cc438cd132ec04d7669ada9e32502336e78713a3ad9b51d10 languageName: node linkType: hard @@ -1309,12 +1309,12 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:^18.2.4": - version: 18.2.4 - resolution: "@types/react-dom@npm:18.2.4" +"@types/react-dom@npm:^18.2.5": + version: 18.2.5 + resolution: "@types/react-dom@npm:18.2.5" dependencies: "@types/react": "*" - checksum: dfeaabb4268d39bdd5addc6c0b7099d5c57a364e70f1087b7c3ee189374312dc65201abfd3d87fee0de11d27c225678ce39c22d14b3035cde5792678704c27b5 + checksum: 7f438f695c91735ff16e6465573a4378fabad6709d99dd08bee4932967ca7e4ccc26dc248f4b88569529885bbca9b1aca0075bee7b833bfa932a558c49d2a066 languageName: node linkType: hard @@ -1368,14 +1368,14 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^18.2.11": - version: 18.2.11 - resolution: "@types/react@npm:18.2.11" +"@types/react@npm:^18.2.12": + version: 18.2.12 + resolution: "@types/react@npm:18.2.12" dependencies: "@types/prop-types": "*" "@types/scheduler": "*" csstype: ^3.0.2 - checksum: 9360d7be13195050eb16598796056123ee9d30470e7073a914300fed9282585d0dd0638bb5ff65843e308c3ac213d25b3388e8186f3134490c758f18f11f3bd8 + checksum: dbaefc7732f77cd0c2bdf6e704ab4ee0f611540777c0b9506d972f165144b6fd6883a29dad61f6965c974c040264852aa8def1e3866b5775b3965a4e67bf92f9 languageName: node linkType: hard @@ -1393,14 +1393,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/eslint-plugin@npm:5.59.9" +"@typescript-eslint/eslint-plugin@npm:^5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/eslint-plugin@npm:5.59.11" dependencies: "@eslint-community/regexpp": ^4.4.0 - "@typescript-eslint/scope-manager": 5.59.9 - "@typescript-eslint/type-utils": 5.59.9 - "@typescript-eslint/utils": 5.59.9 + "@typescript-eslint/scope-manager": 5.59.11 + "@typescript-eslint/type-utils": 5.59.11 + "@typescript-eslint/utils": 5.59.11 debug: ^4.3.4 grapheme-splitter: ^1.0.4 ignore: ^5.2.0 @@ -1413,43 +1413,43 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 4bb9981bcc009c044ffd6b64288309480df2b6c9cdf6b345987e4b565d0973d1d98b7209f6b46b92880735d788f564e17553641087aa59f67990c84526622a27 + checksum: 77f29b8bde5d11a5f222b21e5a3546e86f79a07efd8352b3ebcde67fd2c568ba7c6946fa992dbb9d7d80b6f2455017266fdede1ed134bd181b03f98d81fe71c3 languageName: node linkType: hard -"@typescript-eslint/parser@npm:^5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/parser@npm:5.59.9" +"@typescript-eslint/parser@npm:^5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/parser@npm:5.59.11" dependencies: - "@typescript-eslint/scope-manager": 5.59.9 - "@typescript-eslint/types": 5.59.9 - "@typescript-eslint/typescript-estree": 5.59.9 + "@typescript-eslint/scope-manager": 5.59.11 + "@typescript-eslint/types": 5.59.11 + "@typescript-eslint/typescript-estree": 5.59.11 debug: ^4.3.4 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: af0e041e8a541734ff237ec0eac47e355c2f78dd2b0db4eb4ab0c10ba1b6d5d70f84ddc16f856bc72c4cacd53ef04b5f4948baffb5c8cb2d9a0ffd83a8fbc547 + checksum: 3deea3a9b694ea97e315881ba37d0a90d7f37f0acbb5990270f44c79db9fc3d5675df856f8d1fb7d92c8d38dc63226a42402d57633513981ee526c06e6e8f3c3 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/scope-manager@npm:5.59.9" +"@typescript-eslint/scope-manager@npm:5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/scope-manager@npm:5.59.11" dependencies: - "@typescript-eslint/types": 5.59.9 - "@typescript-eslint/visitor-keys": 5.59.9 - checksum: 41622fd270e5b8574347ed5dd020bbb9752d85e6f40df180e944c1110d9bd2227a949067feb23dd4117dd2be0623c05a47bc363abe605c96deb295753f6dd080 + "@typescript-eslint/types": 5.59.11 + "@typescript-eslint/visitor-keys": 5.59.11 + checksum: e0173e9beb44408ba3e9a1eb173dd83f5c0281af09f936d03f635b7fac41abe1f829a88d0cba134f250dfa4ce527d8cfa32c96e7ada5de876a7aa8bcc933db3b languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/type-utils@npm:5.59.9" +"@typescript-eslint/type-utils@npm:5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/type-utils@npm:5.59.11" dependencies: - "@typescript-eslint/typescript-estree": 5.59.9 - "@typescript-eslint/utils": 5.59.9 + "@typescript-eslint/typescript-estree": 5.59.11 + "@typescript-eslint/utils": 5.59.11 debug: ^4.3.4 tsutils: ^3.21.0 peerDependencies: @@ -1457,23 +1457,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: c3a9773d2b81350923025933623e1572538f79bf119b40bed17389eda11632f6d364a49b385aa6d915d85f7c3d45376085cc55263d865dbc2b753598bba6473b + checksum: 9675cf17970bbf01814d8c8a94aa076fb7c5f5ab8c405800f972a68a72748db07070a526aa2c94d30b2e5ba43bdbef3929a588182bffc3387288b24223574f52 languageName: node linkType: hard -"@typescript-eslint/types@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/types@npm:5.59.9" - checksum: 951046891bcc9fa27d72a5489b496291e44cedcff204d3ce6c10c8916fc5e255332738efd4d7555200a55b49ff4ba1204e186960d216d51fea89fe92a982180e +"@typescript-eslint/types@npm:5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/types@npm:5.59.11" + checksum: 8d007c6c66323582d526429d059c477dcb522b904938a6aaf29804027e6f427533fabe9eb3c987491df74d4288dab238c8c948886f03244a1d908f2985631368 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/typescript-estree@npm:5.59.9" +"@typescript-eslint/typescript-estree@npm:5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/typescript-estree@npm:5.59.11" dependencies: - "@typescript-eslint/types": 5.59.9 - "@typescript-eslint/visitor-keys": 5.59.9 + "@typescript-eslint/types": 5.59.11 + "@typescript-eslint/visitor-keys": 5.59.11 debug: ^4.3.4 globby: ^11.1.0 is-glob: ^4.0.3 @@ -1482,35 +1482,35 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 2f3d8df6d454fbc52d305abfe8447bff8e8d63294ce47e4679c920f647643f5d15a1f693caf74f4fabece12d5ba27ebdb156d507b16fbd2751fc01ba6c4df3c8 + checksum: 4306317ad65668a05d9ec7b280eab94dd7e0d15394db633864bad5d9633fbb9dc516e6cd9f8a0fc2f7a4fc0d53658e5dac61cbee3e070b0b7ac99bdbb0bb45ef languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/utils@npm:5.59.9" +"@typescript-eslint/utils@npm:5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/utils@npm:5.59.11" dependencies: "@eslint-community/eslint-utils": ^4.2.0 "@types/json-schema": ^7.0.9 "@types/semver": ^7.3.12 - "@typescript-eslint/scope-manager": 5.59.9 - "@typescript-eslint/types": 5.59.9 - "@typescript-eslint/typescript-estree": 5.59.9 + "@typescript-eslint/scope-manager": 5.59.11 + "@typescript-eslint/types": 5.59.11 + "@typescript-eslint/typescript-estree": 5.59.11 eslint-scope: ^5.1.1 semver: ^7.3.7 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: b8a04a83c121faa3e36abb2b6113f2e0ec5cf86884d0cb8619bfc50f7442341ee17e4495d69f8abeb6edad9e0347de8382ea1708a5fd6da1e4c80b7b8215c6ab + checksum: c1927950d97afcf3cfc4c3901bc1932caa32bfc533f950f7dab420c478097d3d8daf030c27489e0d49ecdc1f87c52c782833cc505b245ab2a3094c707b0d776e languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/visitor-keys@npm:5.59.9" +"@typescript-eslint/visitor-keys@npm:5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/visitor-keys@npm:5.59.11" dependencies: - "@typescript-eslint/types": 5.59.9 + "@typescript-eslint/types": 5.59.11 eslint-visitor-keys: ^3.3.0 - checksum: 882fd03830cbe0eca8f9a547aecc6519ddbec10e55f5f3de66e605a3f3d42a6237abd3c09b34d9cc3343c8e11386e999876aec384efe523e1478cb22752d326d + checksum: 1644c5bcb87e26717bc587f1ed1d87c96e89dae7d5bdafc1eed35a7c49e3157f8c37936b3897571892f412b7dd8439ba9cf8903128bb847831696f6517bb2d7a languageName: node linkType: hard @@ -1533,17 +1533,17 @@ __metadata: "@emotion/react": ^11.11.1 "@emotion/styled": ^11.11.0 "@mui/icons-material": ^5.11.16 - "@mui/material": ^5.13.4 + "@mui/material": ^5.13.5 "@table-library/react-table-library": 4.1.4 "@types/lodash-es": ^4.17.7 - "@types/node": ^20.3.0 - "@types/react": ^18.2.11 - "@types/react-dom": ^18.2.4 + "@types/node": ^20.3.1 + "@types/react": ^18.2.12 + "@types/react-dom": ^18.2.5 "@types/react-router-dom": ^5.3.3 - "@typescript-eslint/eslint-plugin": ^5.59.9 - "@typescript-eslint/parser": ^5.59.9 + "@typescript-eslint/eslint-plugin": ^5.59.11 + "@typescript-eslint/parser": ^5.59.11 "@vitejs/plugin-react-swc": ^3.3.2 - alova: ^2.6.0 + alova: ^2.6.1 async-validator: ^4.2.5 axios: ^1.4.0 eslint: ^8.42.0 @@ -1571,7 +1571,7 @@ __metadata: react-toastify: ^9.1.3 rollup-plugin-visualizer: ^5.9.2 sockette: ^2.0.6 - terser: ^5.17.7 + terser: ^5.18.0 typesafe-i18n: ^5.24.3 typescript: ^5.1.3 vite: ^4.3.9 @@ -1647,10 +1647,10 @@ __metadata: languageName: node linkType: hard -"alova@npm:^2.6.0": - version: 2.6.0 - resolution: "alova@npm:2.6.0" - checksum: a99dd001f094cccbc6166c5cc56ed8d417434f9edf05aa5176992a3a3735600a3b626b41b50dd867b8a86b3edf44cfbd576568349af937626a7023f9b839226b +"alova@npm:^2.6.1": + version: 2.6.1 + resolution: "alova@npm:2.6.1" + checksum: 55504d1cfab8efff3679d5734e7f78891b3b9c581c6669e6a6df6cc854d05c5d275f6645e1347c633f4418a41d418105857c7d96e0f69dc24abeccc06f0a8c18 languageName: node linkType: hard @@ -5536,9 +5536,9 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.17.7": - version: 5.17.7 - resolution: "terser@npm:5.17.7" +"terser@npm:^5.18.0": + version: 5.18.0 + resolution: "terser@npm:5.18.0" dependencies: "@jridgewell/source-map": ^0.3.3 acorn: ^8.8.2 @@ -5546,7 +5546,7 @@ __metadata: source-map-support: ~0.5.20 bin: terser: bin/terser - checksum: 864154a1750daf516012e5add4f0749bfc71e8f4f918973ec3d504db6a148be976adf46ae490e795173eeff59ec579d7d464bb6354c1bb71f8e14ff398409aed + checksum: 37f562843537c57e119b2ed6d96c2113344d9182a83613abd8e933534b89a3c622ee7ee47d4023249c1d34a2dd1b41a0e56fd6d2e2251f48b79fb7671f269b01 languageName: node linkType: hard diff --git a/lib/ESPAsyncWebServer/WebResponses.cpp b/lib/ESPAsyncWebServer/WebResponses.cpp index d2c2f78a9..63bdcd0ff 100644 --- a/lib/ESPAsyncWebServer/WebResponses.cpp +++ b/lib/ESPAsyncWebServer/WebResponses.cpp @@ -23,225 +23,281 @@ #include "cbuf.h" // Since ESP8266 does not link memchr by default, here's its implementation. -void* memchr(void* ptr, int ch, size_t count) -{ - unsigned char* p = static_cast(ptr); - while(count--) - if(*p++ == static_cast(ch)) - return --p; - return nullptr; +void * memchr(void * ptr, int ch, size_t count) { + unsigned char * p = static_cast(ptr); + while (count--) + if (*p++ == static_cast(ch)) + return --p; + return nullptr; } /* * Abstract Response * */ -const char* AsyncWebServerResponse::_responseCodeToString(int code) { - switch (code) { - case 100: return ("Continue"); - case 101: return ("Switching Protocols"); - case 200: return ("OK"); - case 201: return ("Created"); - case 202: return ("Accepted"); - case 203: return ("Non-Authoritative Information"); - case 204: return ("No Content"); - case 205: return ("Reset Content"); - case 206: return ("Partial Content"); - case 300: return ("Multiple Choices"); - case 301: return ("Moved Permanently"); - case 302: return ("Found"); - case 303: return ("See Other"); - case 304: return ("Not Modified"); - case 305: return ("Use Proxy"); - case 307: return ("Temporary Redirect"); - case 400: return ("Bad Request"); - case 401: return ("Unauthorized"); - case 402: return ("Payment Required"); - case 403: return ("Forbidden"); - case 404: return ("Not Found"); - case 405: return ("Method Not Allowed"); - case 406: return ("Not Acceptable"); - case 407: return ("Proxy Authentication Required"); - case 408: return ("Request Time-out"); - case 409: return ("Conflict"); - case 410: return ("Gone"); - case 411: return ("Length Required"); - case 412: return ("Precondition Failed"); - case 413: return ("Request Entity Too Large"); - case 414: return ("Request-URI Too Large"); - case 415: return ("Unsupported Media Type"); - case 416: return ("Requested range not satisfiable"); - case 417: return ("Expectation Failed"); - case 500: return ("Internal Server Error"); - case 501: return ("Not Implemented"); - case 502: return ("Bad Gateway"); - case 503: return ("Service Unavailable"); - case 504: return ("Gateway Time-out"); - case 505: return ("HTTP Version not supported"); - case 507: return ("Insufficient Storage"); - default: return (""); - } +const char * AsyncWebServerResponse::_responseCodeToString(int code) { + switch (code) { + case 100: + return ("Continue"); + case 101: + return ("Switching Protocols"); + case 200: + return ("OK"); + case 201: + return ("Created"); + case 202: + return ("Accepted"); // proddy: used in wifi + case 203: + return ("Non-Authoritative Information"); + case 204: + return ("No Content"); + case 205: + return ("Reset Content"); // proddy: reboot required + case 206: + return ("Partial Content"); + case 300: + return ("Multiple Choices"); + case 301: + return ("Moved Permanently"); + case 302: + return ("Found"); + case 303: + return ("See Other"); + case 304: + return ("Not Modified"); + case 305: + return ("Use Proxy"); + case 307: + return ("Temporary Redirect"); + case 400: + return ("Bad Request"); + case 401: + return ("Unauthorized"); + case 402: + return ("Payment Required"); + case 403: + return ("Forbidden"); + case 404: + return ("Not Found"); + case 405: + return ("Method Not Allowed"); + case 406: + return ("Not Acceptable"); + case 407: + return ("Proxy Authentication Required"); + case 408: + return ("Request Time-out"); + case 409: + return ("Conflict"); + case 410: + return ("Gone"); + case 411: + return ("Length Required"); + case 412: + return ("Precondition Failed"); + case 413: + return ("Request Entity Too Large"); + case 414: + return ("Request-URI Too Large"); + case 415: + return ("Unsupported Media Type"); + case 416: + return ("Requested range not satisfiable"); + case 417: + return ("Expectation Failed"); + case 500: + return ("Internal Server Error"); + case 501: + return ("Not Implemented"); + case 502: + return ("Bad Gateway"); + case 503: + return ("Service Unavailable"); + case 504: + return ("Gateway Time-out"); + case 505: + return ("HTTP Version not supported"); + case 507: + return ("Insufficient Storage"); + default: + return (""); + } } -const __FlashStringHelper *AsyncWebServerResponse::responseCodeToString(int code) { - return reinterpret_cast(responseCodeToString(code)); +const __FlashStringHelper * AsyncWebServerResponse::responseCodeToString(int code) { + return reinterpret_cast(responseCodeToString(code)); } AsyncWebServerResponse::AsyncWebServerResponse() - : _code(0) - , _headers(LinkedList([](AsyncWebHeader *h){ delete h; })) - , _contentType() - , _contentLength(0) - , _sendContentLength(true) - , _chunked(false) - , _headLength(0) - , _sentLength(0) - , _ackedLength(0) - , _writtenLength(0) - , _state(RESPONSE_SETUP) -{ - for(auto header: DefaultHeaders::Instance()) { - _headers.add(new AsyncWebHeader(header->name(), header->value())); - } + : _code(0) + , _headers(LinkedList([](AsyncWebHeader * h) { delete h; })) + , _contentType() + , _contentLength(0) + , _sendContentLength(true) + , _chunked(false) + , _headLength(0) + , _sentLength(0) + , _ackedLength(0) + , _writtenLength(0) + , _state(RESPONSE_SETUP) { + for (auto header : DefaultHeaders::Instance()) { + _headers.add(new AsyncWebHeader(header->name(), header->value())); + } } -AsyncWebServerResponse::~AsyncWebServerResponse(){ - _headers.free(); +AsyncWebServerResponse::~AsyncWebServerResponse() { + _headers.free(); } -void AsyncWebServerResponse::setCode(int code){ - if(_state == RESPONSE_SETUP) - _code = code; +void AsyncWebServerResponse::setCode(int code) { + if (_state == RESPONSE_SETUP) + _code = code; } -void AsyncWebServerResponse::setContentLength(size_t len){ - if(_state == RESPONSE_SETUP) - _contentLength = len; +void AsyncWebServerResponse::setContentLength(size_t len) { + if (_state == RESPONSE_SETUP) + _contentLength = len; } -void AsyncWebServerResponse::setContentType(const String& type){ - if(_state == RESPONSE_SETUP) - _contentType = type; +void AsyncWebServerResponse::setContentType(const String & type) { + if (_state == RESPONSE_SETUP) + _contentType = type; } -void AsyncWebServerResponse::addHeader(const String& name, const String& value){ - _headers.add(new AsyncWebHeader(name, value)); +void AsyncWebServerResponse::addHeader(const String & name, const String & value) { + _headers.add(new AsyncWebHeader(name, value)); } -String AsyncWebServerResponse::_assembleHead(uint8_t version){ - if(version){ - addHeader(F("Accept-Ranges"), F("none")); - if(_chunked) - addHeader(F("Transfer-Encoding"), F("chunked")); - } - String out = String(); - int bufSize = 300; - char buf[bufSize]; +String AsyncWebServerResponse::_assembleHead(uint8_t version) { + if (version) { + addHeader(F("Accept-Ranges"), F("none")); + if (_chunked) + addHeader(F("Transfer-Encoding"), F("chunked")); + } + String out = String(); + int bufSize = 300; + char buf[bufSize]; - snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code)); - out.concat(buf); - - if(_sendContentLength) { - snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength); + snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code)); out.concat(buf); - } - if(_contentType.length()) { - snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str()); - out.concat(buf); - } - for(const auto& header: _headers){ - snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header->name().c_str(), header->value().c_str()); - out.concat(buf); - } - _headers.free(); + if (_sendContentLength) { + snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength); + out.concat(buf); + } + if (_contentType.length()) { + snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str()); + out.concat(buf); + } - out.concat(F("\r\n")); - _headLength = out.length(); - return out; + for (const auto & header : _headers) { + snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header->name().c_str(), header->value().c_str()); + out.concat(buf); + } + _headers.free(); + + out.concat(F("\r\n")); + _headLength = out.length(); + return out; } -bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; } -bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; } -bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; } -bool AsyncWebServerResponse::_sourceValid() const { return false; } -void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); } -size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ (void)request; (void)len; (void)time; return 0; } +bool AsyncWebServerResponse::_started() const { + return _state > RESPONSE_SETUP; +} +bool AsyncWebServerResponse::_finished() const { + return _state > RESPONSE_WAIT_ACK; +} +bool AsyncWebServerResponse::_failed() const { + return _state == RESPONSE_FAILED; +} +bool AsyncWebServerResponse::_sourceValid() const { + return false; +} +void AsyncWebServerResponse::_respond(AsyncWebServerRequest * request) { + _state = RESPONSE_END; + request->client()->close(); +} +size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) { + (void)request; + (void)len; + (void)time; + return 0; +} /* * String/Code Response * */ -AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content){ - _code = code; - _content = content; - _contentType = contentType; - if(_content.length()){ - _contentLength = _content.length(); - if(!_contentType.length()) - _contentType = F("text/plain"); - } - addHeader(F("Connection"), F("close")); +AsyncBasicResponse::AsyncBasicResponse(int code, const String & contentType, const String & content) { + _code = code; + _content = content; + _contentType = contentType; + if (_content.length()) { + _contentLength = _content.length(); + if (!_contentType.length()) + _contentType = F("text/plain"); + } + addHeader(F("Connection"), F("close")); } -void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ - _state = RESPONSE_HEADERS; - String out = _assembleHead(request->version()); - size_t outLen = out.length(); - size_t space = request->client()->space(); - if(!_contentLength && space >= outLen){ - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_WAIT_ACK; - } else if(_contentLength && space >= outLen + _contentLength){ - out += _content; - outLen += _contentLength; - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_WAIT_ACK; - } else if(space && space < outLen){ - String partial = out.substring(0, space); - _content = out.substring(space) + _content; - _contentLength += outLen - space; - _writtenLength += request->client()->write(partial.c_str(), partial.length()); - _state = RESPONSE_CONTENT; - } else if(space > outLen && space < (outLen + _contentLength)){ - size_t shift = space - outLen; - outLen += shift; - _sentLength += shift; - out += _content.substring(0, shift); - _content = _content.substring(shift); - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_CONTENT; - } else { - _content = out + _content; - _contentLength += outLen; - _state = RESPONSE_CONTENT; - } +void AsyncBasicResponse::_respond(AsyncWebServerRequest * request) { + _state = RESPONSE_HEADERS; + String out = _assembleHead(request->version()); + size_t outLen = out.length(); + size_t space = request->client()->space(); + if (!_contentLength && space >= outLen) { + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if (_contentLength && space >= outLen + _contentLength) { + out += _content; + outLen += _contentLength; + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if (space && space < outLen) { + String partial = out.substring(0, space); + _content = out.substring(space) + _content; + _contentLength += outLen - space; + _writtenLength += request->client()->write(partial.c_str(), partial.length()); + _state = RESPONSE_CONTENT; + } else if (space > outLen && space < (outLen + _contentLength)) { + size_t shift = space - outLen; + outLen += shift; + _sentLength += shift; + out += _content.substring(0, shift); + _content = _content.substring(shift); + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_CONTENT; + } else { + _content = out + _content; + _contentLength += outLen; + _state = RESPONSE_CONTENT; + } } -size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ - (void)time; - _ackedLength += len; - if(_state == RESPONSE_CONTENT){ - size_t available = _contentLength - _sentLength; - size_t space = request->client()->space(); - //we can fit in this packet - if(space > available){ - _writtenLength += request->client()->write(_content.c_str(), available); - _content = String(); - _state = RESPONSE_WAIT_ACK; - return available; +size_t AsyncBasicResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) { + (void)time; + _ackedLength += len; + if (_state == RESPONSE_CONTENT) { + size_t available = _contentLength - _sentLength; + size_t space = request->client()->space(); + //we can fit in this packet + if (space > available) { + _writtenLength += request->client()->write(_content.c_str(), available); + _content = String(); + _state = RESPONSE_WAIT_ACK; + return available; + } + //send some data, the rest on ack + String out = _content.substring(0, space); + _content = _content.substring(space); + _sentLength += space; + _writtenLength += request->client()->write(out.c_str(), space); + return space; + } else if (_state == RESPONSE_WAIT_ACK) { + if (_ackedLength >= _writtenLength) { + _state = RESPONSE_END; + } } - //send some data, the rest on ack - String out = _content.substring(0, space); - _content = _content.substring(space); - _sentLength += space; - _writtenLength += request->client()->write(out.c_str(), space); - return space; - } else if(_state == RESPONSE_WAIT_ACK){ - if(_ackedLength >= _writtenLength){ - _state = RESPONSE_END; - } - } - return 0; + return 0; } @@ -249,232 +305,231 @@ size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint * Abstract Response * */ -AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback) -{ - // In case of template processing, we're unable to determine real response size - if(callback) { - _contentLength = 0; - _sendContentLength = false; - _chunked = true; - } -} - -void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){ - addHeader(F("Connection"), F("close")); - _head = _assembleHead(request->version()); - _state = RESPONSE_HEADERS; - _ack(request, 0, 0); -} - -size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ - (void)time; - if(!_sourceValid()){ - _state = RESPONSE_FAILED; - request->client()->close(); - return 0; - } - _ackedLength += len; - size_t space = request->client()->space(); - - size_t headLen = _head.length(); - if(_state == RESPONSE_HEADERS){ - if(space >= headLen){ - _state = RESPONSE_CONTENT; - space -= headLen; - } else { - String out = _head.substring(0, space); - _head = _head.substring(space); - _writtenLength += request->client()->write(out.c_str(), out.length()); - return out.length(); +AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) + : _callback(callback) { + // In case of template processing, we're unable to determine real response size + if (callback) { + _contentLength = 0; + _sendContentLength = false; + _chunked = true; } - } +} - if(_state == RESPONSE_CONTENT){ - size_t outLen; - if(_chunked){ - if(space <= 8){ +void AsyncAbstractResponse::_respond(AsyncWebServerRequest * request) { + addHeader(F("Connection"), F("close")); + _head = _assembleHead(request->version()); + _state = RESPONSE_HEADERS; + _ack(request, 0, 0); +} + +size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) { + (void)time; + if (!_sourceValid()) { + _state = RESPONSE_FAILED; + request->client()->close(); return 0; - } - outLen = space; - } else if(!_sendContentLength){ - outLen = space; - } else { - outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength); + } + _ackedLength += len; + size_t space = request->client()->space(); + + size_t headLen = _head.length(); + if (_state == RESPONSE_HEADERS) { + if (space >= headLen) { + _state = RESPONSE_CONTENT; + space -= headLen; + } else { + String out = _head.substring(0, space); + _head = _head.substring(space); + _writtenLength += request->client()->write(out.c_str(), out.length()); + return out.length(); + } } - uint8_t *buf = (uint8_t *)malloc(outLen+headLen); - if (!buf) { - // os_printf("_ack malloc %d failed\n", outLen+headLen); - return 0; + if (_state == RESPONSE_CONTENT) { + size_t outLen; + if (_chunked) { + if (space <= 8) { + return 0; + } + outLen = space; + } else if (!_sendContentLength) { + outLen = space; + } else { + outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength); + } + + uint8_t * buf = (uint8_t *)malloc(outLen + headLen); + if (!buf) { + // os_printf("_ack malloc %d failed\n", outLen+headLen); + return 0; + } + + if (headLen) { + memcpy(buf, _head.c_str(), _head.length()); + } + + size_t readLen = 0; + + if (_chunked) { + // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. + // See RFC2616 sections 2, 3.6.1. + readLen = _fillBufferAndProcessTemplates(buf + headLen + 6, outLen - 8); + if (readLen == RESPONSE_TRY_AGAIN) { + free(buf); + return 0; + } + outLen = snprintf_P((char *)buf + headLen, sizeof(buf) - headLen - 2, PSTR("%x"), readLen) + headLen; + while (outLen < headLen + 4) + buf[outLen++] = ' '; + buf[outLen++] = '\r'; + buf[outLen++] = '\n'; + outLen += readLen; + buf[outLen++] = '\r'; + buf[outLen++] = '\n'; + } else { + readLen = _fillBufferAndProcessTemplates(buf + headLen, outLen); + if (readLen == RESPONSE_TRY_AGAIN) { + free(buf); + return 0; + } + outLen = readLen + headLen; + } + + if (headLen) { + _head = String(); + } + + if (outLen) { + _writtenLength += request->client()->write((const char *)buf, outLen); + } + + if (_chunked) { + _sentLength += readLen; + } else { + _sentLength += outLen - headLen; + } + + free(buf); + + if ((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)) { + _state = RESPONSE_WAIT_ACK; + } + return outLen; + + } else if (_state == RESPONSE_WAIT_ACK) { + if (!_sendContentLength || _ackedLength >= _writtenLength) { + _state = RESPONSE_END; + if (!_chunked && !_sendContentLength) + request->client()->close(true); + } } - - if(headLen){ - memcpy(buf, _head.c_str(), _head.length()); - } - - size_t readLen = 0; - - if(_chunked){ - // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. - // See RFC2616 sections 2, 3.6.1. - readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8); - if(readLen == RESPONSE_TRY_AGAIN){ - free(buf); - return 0; - } - outLen = snprintf_P((char*)buf+headLen, sizeof(buf)-headLen-2, PSTR("%x"), readLen) + headLen; - while(outLen < headLen + 4) buf[outLen++] = ' '; - buf[outLen++] = '\r'; - buf[outLen++] = '\n'; - outLen += readLen; - buf[outLen++] = '\r'; - buf[outLen++] = '\n'; - } else { - readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen); - if(readLen == RESPONSE_TRY_AGAIN){ - free(buf); - return 0; - } - outLen = readLen + headLen; - } - - if(headLen){ - _head = String(); - } - - if(outLen){ - _writtenLength += request->client()->write((const char*)buf, outLen); - } - - if(_chunked){ - _sentLength += readLen; - } else { - _sentLength += outLen - headLen; - } - - free(buf); - - if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){ - _state = RESPONSE_WAIT_ACK; - } - return outLen; - - } else if(_state == RESPONSE_WAIT_ACK){ - if(!_sendContentLength || _ackedLength >= _writtenLength){ - _state = RESPONSE_END; - if(!_chunked && !_sendContentLength) - request->client()->close(true); - } - } - return 0; + return 0; } -size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) -{ +size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t * data, const size_t len) { // If we have something in cache, copy it to buffer const size_t readFromCache = std::min(len, _cache.size()); - if(readFromCache) { - memcpy(data, _cache.data(), readFromCache); - _cache.erase(_cache.begin(), _cache.begin() + readFromCache); + if (readFromCache) { + memcpy(data, _cache.data(), readFromCache); + _cache.erase(_cache.begin(), _cache.begin() + readFromCache); } // If we need to read more... - const size_t needFromFile = len - readFromCache; + const size_t needFromFile = len - readFromCache; const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile); return readFromCache + readFromContent; } -size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) -{ - if(!_callback) - return _fillBuffer(data, len); +size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t * data, size_t len) { + if (!_callback) + return _fillBuffer(data, len); - const size_t originalLen = len; - len = _readDataFromCacheOrContent(data, len); - // Now we've read 'len' bytes, either from cache or from file - // Search for template placeholders - uint8_t* pTemplateStart = data; - while((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1] - uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; - // temporary buffer to hold parameter name - uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; - String paramName; - // If closing placeholder is found: - if(pTemplateEnd) { - // prepare argument to callback - const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1)); - if(paramNameLength) { - memcpy(buf, pTemplateStart + 1, paramNameLength); - buf[paramNameLength] = 0; - paramName = String(reinterpret_cast(buf)); - } else { // double percent sign encountered, this is single percent sign escaped. - // remove the 2nd percent sign - memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); - len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; - ++pTemplateStart; - } - } else if(&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data - memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart); - const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); - if(readFromCacheOrContent) { - pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); - if(pTemplateEnd) { - // prepare argument to callback - *pTemplateEnd = 0; - paramName = String(reinterpret_cast(buf)); - // Copy remaining read-ahead data into cache - _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); - pTemplateEnd = &data[len - 1]; + const size_t originalLen = len; + len = _readDataFromCacheOrContent(data, len); + // Now we've read 'len' bytes, either from cache or from file + // Search for template placeholders + uint8_t * pTemplateStart = data; + while ((pTemplateStart < &data[len]) + && (pTemplateStart = (uint8_t *)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1] + uint8_t * pTemplateEnd = + (pTemplateStart < &data[len - 1]) ? (uint8_t *)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; + // temporary buffer to hold parameter name + uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; + String paramName; + // If closing placeholder is found: + if (pTemplateEnd) { + // prepare argument to callback + const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1)); + if (paramNameLength) { + memcpy(buf, pTemplateStart + 1, paramNameLength); + buf[paramNameLength] = 0; + paramName = String(reinterpret_cast(buf)); + } else { // double percent sign encountered, this is single percent sign escaped. + // remove the 2nd percent sign + memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); + len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; + ++pTemplateStart; + } + } else if (&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data + memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart); + const size_t readFromCacheOrContent = + _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); + if (readFromCacheOrContent) { + pTemplateEnd = (uint8_t *)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); + if (pTemplateEnd) { + // prepare argument to callback + *pTemplateEnd = 0; + paramName = String(reinterpret_cast(buf)); + // Copy remaining read-ahead data into cache + _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); + pTemplateEnd = &data[len - 1]; + } else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position + { + // but first, store read file data in cache + _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); + ++pTemplateStart; + } + } else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position + ++pTemplateStart; + } else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position + ++pTemplateStart; + if (paramName.length()) { + // call callback and replace with result. + // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. + // Data after pTemplateEnd may need to be moved. + // The first byte of data after placeholder is located at pTemplateEnd + 1. + // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value). + const String paramValue(_callback(paramName)); + const char * pvstr = paramValue.c_str(); + const unsigned int pvlen = paramValue.length(); + const size_t numBytesCopied = std::min(pvlen, static_cast(&data[originalLen - 1] - pTemplateStart + 1)); + // make room for param value + // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store + if ((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { + _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); + //2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end + memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); + len = originalLen; // fix issue with truncated data, not sure if it has any side effects + } else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied) + //2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit. + // Move the entire data after the placeholder + memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); + // 3. replace placeholder with actual value + memcpy(pTemplateStart, pvstr, numBytesCopied); + // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer) + if (numBytesCopied < pvlen) { + _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); + } else if (pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text... + // there is some free room, fill it from cache + const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; + const size_t totalFreeRoom = originalLen - len + roomFreed; + len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed; + } else { // result is copied fully; it is longer than placeholder text + const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1; + len = std::min(len + roomTaken, originalLen); + } } - else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position - { - // but first, store read file data in cache - _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); - ++pTemplateStart; - } - } - else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position - ++pTemplateStart; - } - else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position - ++pTemplateStart; - if(paramName.length()) { - // call callback and replace with result. - // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. - // Data after pTemplateEnd may need to be moved. - // The first byte of data after placeholder is located at pTemplateEnd + 1. - // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value). - const String paramValue(_callback(paramName)); - const char* pvstr = paramValue.c_str(); - const unsigned int pvlen = paramValue.length(); - const size_t numBytesCopied = std::min(pvlen, static_cast(&data[originalLen - 1] - pTemplateStart + 1)); - // make room for param value - // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store - if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { - _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); - //2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end - memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); - len = originalLen; // fix issue with truncated data, not sure if it has any side effects - } else if(pTemplateEnd + 1 != pTemplateStart + numBytesCopied) - //2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit. - // Move the entire data after the placeholder - memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); - // 3. replace placeholder with actual value - memcpy(pTemplateStart, pvstr, numBytesCopied); - // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer) - if(numBytesCopied < pvlen) { - _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); - } else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text... - // there is some free room, fill it from cache - const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; - const size_t totalFreeRoom = originalLen - len + roomFreed; - len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed; - } else { // result is copied fully; it is longer than placeholder text - const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1; - len = std::min(len + roomTaken, originalLen); - } - } - } // while(pTemplateStart) - return len; + } // while(pTemplateStart) + return len; } @@ -482,193 +537,218 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size * File Response * */ -AsyncFileResponse::~AsyncFileResponse(){ - if(_content) - _content.close(); +AsyncFileResponse::~AsyncFileResponse() { + if (_content) + _content.close(); } -void AsyncFileResponse::_setContentType(const String& path){ +void AsyncFileResponse::_setContentType(const String & path) { #if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION - extern const __FlashStringHelper *getContentType(const String &path); - _contentType = getContentType(path); + extern const __FlashStringHelper * getContentType(const String & path); + _contentType = getContentType(path); #else - if (path.endsWith(F(".html"))) _contentType = F("text/html"); - else if (path.endsWith(F(".htm"))) _contentType = F("text/html"); - else if (path.endsWith(F(".css"))) _contentType = F("text/css"); - else if (path.endsWith(F(".json"))) _contentType = F("application/json"); - else if (path.endsWith(F(".js"))) _contentType = F("application/javascript"); - else if (path.endsWith(F(".png"))) _contentType = F("image/png"); - else if (path.endsWith(F(".gif"))) _contentType = F("image/gif"); - else if (path.endsWith(F(".jpg"))) _contentType = F("image/jpeg"); - else if (path.endsWith(F(".ico"))) _contentType = F("image/x-icon"); - else if (path.endsWith(F(".svg"))) _contentType = F("image/svg+xml"); - else if (path.endsWith(F(".eot"))) _contentType = F("font/eot"); - else if (path.endsWith(F(".woff"))) _contentType = F("font/woff"); - else if (path.endsWith(F(".woff2"))) _contentType = F("font/woff2"); - else if (path.endsWith(F(".ttf"))) _contentType = F("font/ttf"); - else if (path.endsWith(F(".xml"))) _contentType = F("text/xml"); - else if (path.endsWith(F(".pdf"))) _contentType = F("application/pdf"); - else if (path.endsWith(F(".zip"))) _contentType = F("application/zip"); - else if(path.endsWith(F(".gz"))) _contentType = F("application/x-gzip"); - else _contentType = F("text/plain"); + if (path.endsWith(F(".html"))) + _contentType = F("text/html"); + else if (path.endsWith(F(".htm"))) + _contentType = F("text/html"); + else if (path.endsWith(F(".css"))) + _contentType = F("text/css"); + else if (path.endsWith(F(".json"))) + _contentType = F("application/json"); + else if (path.endsWith(F(".js"))) + _contentType = F("application/javascript"); + else if (path.endsWith(F(".png"))) + _contentType = F("image/png"); + else if (path.endsWith(F(".gif"))) + _contentType = F("image/gif"); + else if (path.endsWith(F(".jpg"))) + _contentType = F("image/jpeg"); + else if (path.endsWith(F(".ico"))) + _contentType = F("image/x-icon"); + else if (path.endsWith(F(".svg"))) + _contentType = F("image/svg+xml"); + else if (path.endsWith(F(".eot"))) + _contentType = F("font/eot"); + else if (path.endsWith(F(".woff"))) + _contentType = F("font/woff"); + else if (path.endsWith(F(".woff2"))) + _contentType = F("font/woff2"); + else if (path.endsWith(F(".ttf"))) + _contentType = F("font/ttf"); + else if (path.endsWith(F(".xml"))) + _contentType = F("text/xml"); + else if (path.endsWith(F(".pdf"))) + _contentType = F("application/pdf"); + else if (path.endsWith(F(".zip"))) + _contentType = F("application/zip"); + else if (path.endsWith(F(".gz"))) + _contentType = F("application/x-gzip"); + else + _contentType = F("text/plain"); #endif } -AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ - _code = 200; - _path = path; +AsyncFileResponse::AsyncFileResponse(FS & fs, const String & path, const String & contentType, bool download, AwsTemplateProcessor callback) + : AsyncAbstractResponse(callback) { + _code = 200; + _path = path; - if(!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))){ - _path = _path + F(".gz"); - addHeader(F("Content-Encoding"), F("gzip")); - _callback = nullptr; // Unable to process zipped templates - _sendContentLength = true; - _chunked = false; - } + if (!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))) { + _path = _path + F(".gz"); + addHeader(F("Content-Encoding"), F("gzip")); + _callback = nullptr; // Unable to process zipped templates + _sendContentLength = true; + _chunked = false; + } - _content = fs.open(_path, fs::FileOpenMode::read); - _contentLength = _content.size(); + _content = fs.open(_path, fs::FileOpenMode::read); + _contentLength = _content.size(); - if(contentType.length() == 0) - _setContentType(path); - else - _contentType = contentType; + if (contentType.length() == 0) + _setContentType(path); + else + _contentType = contentType; - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26+path.length()-filenameStart]; - char* filename = (char*)path.c_str() + filenameStart; + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26 + path.length() - filenameStart]; + char * filename = (char *)path.c_str() + filenameStart; - if(download) { - // set filename and force download - snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); - } else { - // set filename and force rendering - snprintf_P(buf, sizeof (buf), PSTR("inline; filename=\"%s\""), filename); - } - addHeader(F("Content-Disposition"), buf); + if (download) { + // set filename and force download + snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename); + } else { + // set filename and force rendering + snprintf_P(buf, sizeof(buf), PSTR("inline; filename=\"%s\""), filename); + } + addHeader(F("Content-Disposition"), buf); } -AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ - _code = 200; - _path = path; +AsyncFileResponse::AsyncFileResponse(File content, const String & path, const String & contentType, bool download, AwsTemplateProcessor callback) + : AsyncAbstractResponse(callback) { + _code = 200; + _path = path; - if(!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))){ - addHeader(F("Content-Encoding"), F("gzip")); - _callback = nullptr; // Unable to process gzipped templates - _sendContentLength = true; - _chunked = false; - } + if (!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))) { + addHeader(F("Content-Encoding"), F("gzip")); + _callback = nullptr; // Unable to process gzipped templates + _sendContentLength = true; + _chunked = false; + } - _content = content; - _contentLength = _content.size(); + _content = content; + _contentLength = _content.size(); - if(contentType.length() == 0) - _setContentType(path); - else - _contentType = contentType; + if (contentType.length() == 0) + _setContentType(path); + else + _contentType = contentType; - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26+path.length()-filenameStart]; - char* filename = (char*)path.c_str() + filenameStart; + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26 + path.length() - filenameStart]; + char * filename = (char *)path.c_str() + filenameStart; - if(download) { - snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); - } else { - snprintf_P(buf, sizeof (buf), PSTR("inline; filename=\"%s\""), filename); - } - addHeader(F("Content-Disposition"), buf); + if (download) { + snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename); + } else { + snprintf_P(buf, sizeof(buf), PSTR("inline; filename=\"%s\""), filename); + } + addHeader(F("Content-Disposition"), buf); } -size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){ - return _content.read(data, len); +size_t AsyncFileResponse::_fillBuffer(uint8_t * data, size_t len) { + return _content.read(data, len); } /* * Stream Response * */ -AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { - _code = 200; - _content = &stream; - _contentLength = len; - _contentType = contentType; +AsyncStreamResponse::AsyncStreamResponse(Stream & stream, const String & contentType, size_t len, AwsTemplateProcessor callback) + : AsyncAbstractResponse(callback) { + _code = 200; + _content = &stream; + _contentLength = len; + _contentType = contentType; } -size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t available = _content->available(); - size_t outLen = (available > len)?len:available; - size_t i; - for(i=0;iread(); - return outLen; +size_t AsyncStreamResponse::_fillBuffer(uint8_t * data, size_t len) { + size_t available = _content->available(); + size_t outLen = (available > len) ? len : available; + size_t i; + for (i = 0; i < outLen; i++) + data[i] = _content->read(); + return outLen; } /* * Callback Response * */ -AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) { - _code = 200; - _content = callback; - _contentLength = len; - if(!len) - _sendContentLength = false; - _contentType = contentType; - _filledLength = 0; +AsyncCallbackResponse::AsyncCallbackResponse(const String & contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) + : AsyncAbstractResponse(templateCallback) { + _code = 200; + _content = callback; + _contentLength = len; + if (!len) + _sendContentLength = false; + _contentType = contentType; + _filledLength = 0; } -size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t ret = _content(data, len, _filledLength); - if(ret != RESPONSE_TRY_AGAIN){ - _filledLength += ret; - } - return ret; +size_t AsyncCallbackResponse::_fillBuffer(uint8_t * data, size_t len) { + size_t ret = _content(data, len, _filledLength); + if (ret != RESPONSE_TRY_AGAIN) { + _filledLength += ret; + } + return ret; } /* * Chunked Response * */ -AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) { - _code = 200; - _content = callback; - _contentLength = 0; - _contentType = contentType; - _sendContentLength = false; - _chunked = true; - _filledLength = 0; +AsyncChunkedResponse::AsyncChunkedResponse(const String & contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback) + : AsyncAbstractResponse(processorCallback) { + _code = 200; + _content = callback; + _contentLength = 0; + _contentType = contentType; + _sendContentLength = false; + _chunked = true; + _filledLength = 0; } -size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t ret = _content(data, len, _filledLength); - if(ret != RESPONSE_TRY_AGAIN){ - _filledLength += ret; - } - return ret; +size_t AsyncChunkedResponse::_fillBuffer(uint8_t * data, size_t len) { + size_t ret = _content(data, len, _filledLength); + if (ret != RESPONSE_TRY_AGAIN) { + _filledLength += ret; + } + return ret; } /* * Progmem Response * */ -AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { - _code = code; - _content = content; - _contentType = contentType; - _contentLength = len; - _readLength = 0; +AsyncProgmemResponse::AsyncProgmemResponse(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback) + : AsyncAbstractResponse(callback) { + _code = code; + _content = content; + _contentType = contentType; + _contentLength = len; + _readLength = 0; } -size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t left = _contentLength - _readLength; - if (left > len) { - memcpy_P(data, _content + _readLength, len); - _readLength += len; - return len; - } - memcpy_P(data, _content + _readLength, left); - _readLength += left; - return left; +size_t AsyncProgmemResponse::_fillBuffer(uint8_t * data, size_t len) { + size_t left = _contentLength - _readLength; + if (left > len) { + memcpy_P(data, _content + _readLength, len); + _readLength += len; + return len; + } + memcpy_P(data, _content + _readLength, left); + _readLength += left; + return left; } @@ -676,34 +756,34 @@ size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){ * Response Stream (You can print/write/printf to it, up to the contentLen bytes) * */ -AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize){ - _code = 200; - _contentLength = 0; - _contentType = contentType; - _content = new cbuf(bufferSize); +AsyncResponseStream::AsyncResponseStream(const String & contentType, size_t bufferSize) { + _code = 200; + _contentLength = 0; + _contentType = contentType; + _content = new cbuf(bufferSize); } -AsyncResponseStream::~AsyncResponseStream(){ - delete _content; +AsyncResponseStream::~AsyncResponseStream() { + delete _content; } -size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){ - return _content->read((char*)buf, maxLen); +size_t AsyncResponseStream::_fillBuffer(uint8_t * buf, size_t maxLen) { + return _content->read((char *)buf, maxLen); } -size_t AsyncResponseStream::write(const uint8_t *data, size_t len){ - if(_started()) - return 0; +size_t AsyncResponseStream::write(const uint8_t * data, size_t len) { + if (_started()) + return 0; - if(len > _content->room()){ - size_t needed = len - _content->room(); - _content->resizeAdd(needed); - } - size_t written = _content->write((const char*)data, len); - _contentLength += written; - return written; + if (len > _content->room()) { + size_t needed = len - _content->room(); + _content->resizeAdd(needed); + } + size_t written = _content->write((const char *)data, len); + _contentLength += written; + return written; } -size_t AsyncResponseStream::write(uint8_t data){ - return write(&data, 1); +size_t AsyncResponseStream::write(uint8_t data) { + return write(&data, 1); } diff --git a/lib/framework/HttpEndpoint.h b/lib/framework/HttpEndpoint.h index 21ac7e610..0e0014049 100644 --- a/lib/framework/HttpEndpoint.h +++ b/lib/framework/HttpEndpoint.h @@ -115,7 +115,7 @@ class HttpPostEndpoint { response->setLength(); if (outcome == StateUpdateResult::CHANGED_RESTART) { - response->setCode(202); // added by proddy + response->setCode(205); // added by proddy, reboot required } request->send(response); } diff --git a/lib/framework/WiFiScanner.cpp b/lib/framework/WiFiScanner.cpp index b7ac8f718..708179686 100644 --- a/lib/framework/WiFiScanner.cpp +++ b/lib/framework/WiFiScanner.cpp @@ -16,7 +16,7 @@ void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) { WiFi.scanDelete(); WiFi.scanNetworks(true); } - request->send(202); + request->send(202); // special code to indicate scan in progress } void WiFiScanner::listNetworks(AsyncWebServerRequest * request) { @@ -36,7 +36,7 @@ void WiFiScanner::listNetworks(AsyncWebServerRequest * request) { response->setLength(); request->send(response); } else if (numNetworks == -1) { - request->send(202); + request->send(202); // special code to indicate scan in progress } else { scanNetworks(request); } diff --git a/mock-api/server.js b/mock-api/server.js index 04ede86d1..c805f7bc6 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -480,7 +480,7 @@ const emsesp_sensordata = { { id: '28-233D-9497-0C03', n: 'Dallas 1', t: 25.7, o: 1.2, u: 1 }, { id: '28-243D-7437-1E3A', n: 'Dallas 2 outside', t: 26.1, o: 0, u: 1 }, { id: '28-243E-7437-1E3B', n: 'Zolder', t: 27.1, o: 0, u: 16 }, - { id: '28-183D-1892-0C33', n: 'Roof', o: 2, u: 1 } + { id: '28-183D-1892-0C33', n: 'Roof', o: 2, u: 1 } // no temperature ], // as: [], as: [ @@ -512,95 +512,15 @@ const status = { }; // Dashboard data -// 7 - Nefit Trendline boiler // 1 - RC35 thermo // 2 - RC20 thermo // 3 - Buderus GB125 boiler // 4 - RC100 themo // 5 - Mixer MM10 // 6 - Solar SM10 +// 7 - Nefit Trendline boiler // 99 - Custom -const emsesp_devicedata_7 = { - data: [ - { v: '', u: 0, id: '08reset', c: 'reset', l: ['-', 'maintenance', 'error'] }, - { v: 'off', u: 0, id: '08heating active' }, - { v: 'off', u: 0, id: '04tapwater active' }, - { v: 5, u: 1, id: '04selected flow temperature', c: 'selflowtemp' }, - { v: 0, u: 3, id: '0Eburner selected max power', c: 'selburnpow' }, - { v: 0, u: 3, id: '00heating pump modulation' }, - { v: 53.4, u: 1, id: '00current flow temperature' }, - { v: 52.7, u: 1, id: '00return temperature' }, - { v: 1.3, u: 10, id: '00system pressure' }, - { v: 54.9, u: 1, id: '00actual boiler temperature' }, - { v: 'off', u: 0, id: '00gas' }, - { v: 'off', u: 0, id: '00gas stage 2' }, - { v: 0, u: 9, id: '00flame current' }, - { v: 'off', u: 0, id: '00heating pump' }, - { v: 'off', u: 0, id: '00fan' }, - { v: 'off', u: 0, id: '00ignition' }, - { v: 'off', u: 0, id: '00oil preheating' }, - { v: 'on', u: 0, id: '00heating activated', c: 'heatingactivated', l: ['off', 'on'] }, - { v: 80, u: 1, id: '00heating temperature', c: 'heatingtemp' }, - { v: 70, u: 3, id: '00burner pump max power', c: 'pumpmodmax' }, - { v: 30, u: 3, id: '00burner pump min power', c: 'pumpmodmin' }, - { v: 1, u: 8, id: '00pump delay', c: 'pumpdelay' }, - { v: 10, u: 8, id: '00burner min period', c: 'burnminperiod' }, - { v: 0, u: 3, id: '00burner min power', c: 'burnminpower' }, - { v: 50, u: 3, id: '00burner max power', c: 'burnmaxpower' }, - { v: -6, u: 2, id: '00hysteresis on temperature', c: 'boilhyston' }, - { v: 6, u: 2, id: '00hysteresis off temperature', c: 'boilhystoff' }, - { v: 0, u: 1, id: '00set flow temperature' }, - { v: 0, u: 3, id: '00burner set power' }, - { v: 0, u: 3, id: '00burner current power' }, - { v: 326323, u: 0, id: '00burner starts' }, - { v: 553437, u: 8, id: '00total burner operating time' }, - { v: 451286, u: 8, id: '00total heat operating time' }, - { v: 4672173, u: 8, id: '00total UBA operating time' }, - { v: '1C(210) 06.06.2020 12:07 (0 min)', u: 0, id: '00last error code' }, - { v: '0H', u: 0, id: '00service code' }, - { v: 203, u: 0, id: '00service code number' }, - { v: 'H00', u: 0, id: '00maintenance message' }, - { v: 'manual', u: 0, id: '00maintenance scheduled', c: 'maintenance', l: ['off', 'time', 'date', 'manual'] }, - { v: 6000, u: 7, id: '00time to next maintenance', c: 'maintenancetime' }, - { v: '01.01.2012', u: 0, id: '00next maintenance date', c: 'maintenancedate', o: 'Format: < dd.mm.yyyy >' }, - { v: 'on', u: 0, id: '00dhw turn on/off', c: 'wwtapactivated', l: ['off', 'on'] }, - { v: 62, u: 1, id: '00dhw set temperature' }, - { v: 60, u: 1, id: '00dhw selected temperature', c: 'wwseltemp' }, - { v: 'flow', u: 0, id: '00dhw type' }, - { v: 'hot', u: 0, id: '00dhw comfort', c: 'wwcomfort', l: ['hot', 'eco', 'intelligent'] }, - { v: 40, u: 2, id: '00dhw flow temperature offset', c: 'wwflowtempoffset' }, - { v: 100, u: 3, id: '00dhw max power', c: 'wwmaxpower' }, - { v: 'off', u: 0, id: '00dhw circulation pump available', c: 'wwcircpump', l: ['off', 'on'] }, - { v: '3-way valve', u: 0, id: '00dhw charging type' }, - { v: -5, u: 2, id: '00dhw hysteresis on temperature', c: 'wwhyston' }, - { v: 0, u: 2, id: '00dhw hysteresis off temperature', c: 'wwhystoff' }, - { v: 70, u: 1, id: '00dhw disinfection temperature', c: 'wwdisinfectiontemp' }, - { - v: 'off', - u: 0, - id: '00dhw circulation pump mode', - c: 'wwcircmode', - l: ['off', '1x3min', '2x3min', '3x3min', '4x3min', '5x3min', '6x3min', 'continuous'] - }, - { v: 'off', u: 0, id: '00dhw circulation active', c: 'wwcirc', l: ['off', 'on'] }, - { v: 47.3, u: 1, id: '00dhw current intern temperature' }, - { v: 0, u: 4, id: '00dhw current tap water flow' }, - { v: 47.3, u: 1, id: '00dhw storage intern temperature' }, - { v: 'on', u: 0, id: '00dhw activated', c: 'wwactivated', l: ['off', 'on'] }, - { v: 'off', u: 0, id: '00dhw one time charging', c: 'wwonetime', l: ['off', 'on'] }, - { v: 'off', u: 0, id: '00dhw disinfecting', c: 'wwdisinfecting', l: ['off', 'on'] }, - { v: 'off', u: 0, id: '00dhw charging' }, - { v: 'off', u: 0, id: '00dhw recharging' }, - { v: 'on', u: 0, id: '00dhw temperature ok' }, - { v: 'off', u: 0, id: '00dhw active' }, - { v: 'on', u: 0, id: '00dhw 3way valve active' }, - { v: 0, u: 3, id: '00dhw set pump power' }, - { v: 288768, u: 0, id: '00dhw starts' }, - { v: 102151, u: 8, id: '00dhw active time' } - ] -}; - const emsesp_devicedata_1 = { data: [ { @@ -1768,6 +1688,86 @@ const emsesp_devicedata_6 = { ] }; +const emsesp_devicedata_7 = { + data: [ + { v: '', u: 0, id: '08reset', c: 'reset', l: ['-', 'maintenance', 'error'] }, + { v: 'off', u: 0, id: '08heating active' }, + { v: 'off', u: 0, id: '04tapwater active' }, + { v: 5, u: 1, id: '04selected flow temperature', c: 'selflowtemp' }, + { v: 0, u: 3, id: '0Eburner selected max power', c: 'selburnpow' }, + { v: 0, u: 3, id: '00heating pump modulation' }, + { v: 53.4, u: 1, id: '00current flow temperature' }, + { v: 52.7, u: 1, id: '00return temperature' }, + { v: 1.3, u: 10, id: '00system pressure' }, + { v: 54.9, u: 1, id: '00actual boiler temperature' }, + { v: 'off', u: 0, id: '00gas' }, + { v: 'off', u: 0, id: '00gas stage 2' }, + { v: 0, u: 9, id: '00flame current' }, + { v: 'off', u: 0, id: '00heating pump' }, + { v: 'off', u: 0, id: '00fan' }, + { v: 'off', u: 0, id: '00ignition' }, + { v: 'off', u: 0, id: '00oil preheating' }, + { v: 'on', u: 0, id: '00heating activated', c: 'heatingactivated', l: ['off', 'on'] }, + { v: 80, u: 1, id: '00heating temperature', c: 'heatingtemp' }, + { v: 70, u: 3, id: '00burner pump max power', c: 'pumpmodmax' }, + { v: 30, u: 3, id: '00burner pump min power', c: 'pumpmodmin' }, + { v: 1, u: 8, id: '00pump delay', c: 'pumpdelay' }, + { v: 10, u: 8, id: '00burner min period', c: 'burnminperiod' }, + { v: 0, u: 3, id: '00burner min power', c: 'burnminpower' }, + { v: 50, u: 3, id: '00burner max power', c: 'burnmaxpower' }, + { v: -6, u: 2, id: '00hysteresis on temperature', c: 'boilhyston' }, + { v: 6, u: 2, id: '00hysteresis off temperature', c: 'boilhystoff' }, + { v: 0, u: 1, id: '00set flow temperature' }, + { v: 0, u: 3, id: '00burner set power' }, + { v: 0, u: 3, id: '00burner current power' }, + { v: 326323, u: 0, id: '00burner starts' }, + { v: 553437, u: 8, id: '00total burner operating time' }, + { v: 451286, u: 8, id: '00total heat operating time' }, + { v: 4672173, u: 8, id: '00total UBA operating time' }, + { v: '1C(210) 06.06.2020 12:07 (0 min)', u: 0, id: '00last error code' }, + { v: '0H', u: 0, id: '00service code' }, + { v: 203, u: 0, id: '00service code number' }, + { v: 'H00', u: 0, id: '00maintenance message' }, + { v: 'manual', u: 0, id: '00maintenance scheduled', c: 'maintenance', l: ['off', 'time', 'date', 'manual'] }, + { v: 6000, u: 7, id: '00time to next maintenance', c: 'maintenancetime' }, + { v: '01.01.2012', u: 0, id: '00next maintenance date', c: 'maintenancedate', o: 'Format: < dd.mm.yyyy >' }, + { v: 'on', u: 0, id: '00dhw turn on/off', c: 'wwtapactivated', l: ['off', 'on'] }, + { v: 62, u: 1, id: '00dhw set temperature' }, + { v: 60, u: 1, id: '00dhw selected temperature', c: 'wwseltemp' }, + { v: 'flow', u: 0, id: '00dhw type' }, + { v: 'hot', u: 0, id: '00dhw comfort', c: 'wwcomfort', l: ['hot', 'eco', 'intelligent'] }, + { v: 40, u: 2, id: '00dhw flow temperature offset', c: 'wwflowtempoffset' }, + { v: 100, u: 3, id: '00dhw max power', c: 'wwmaxpower' }, + { v: 'off', u: 0, id: '00dhw circulation pump available', c: 'wwcircpump', l: ['off', 'on'] }, + { v: '3-way valve', u: 0, id: '00dhw charging type' }, + { v: -5, u: 2, id: '00dhw hysteresis on temperature', c: 'wwhyston' }, + { v: 0, u: 2, id: '00dhw hysteresis off temperature', c: 'wwhystoff' }, + { v: 70, u: 1, id: '00dhw disinfection temperature', c: 'wwdisinfectiontemp' }, + { + v: 'off', + u: 0, + id: '00dhw circulation pump mode', + c: 'wwcircmode', + l: ['off', '1x3min', '2x3min', '3x3min', '4x3min', '5x3min', '6x3min', 'continuous'] + }, + { v: 'off', u: 0, id: '00dhw circulation active', c: 'wwcirc', l: ['off', 'on'] }, + { v: 47.3, u: 1, id: '00dhw current intern temperature' }, + { v: 0, u: 4, id: '00dhw current tap water flow' }, + { v: 47.3, u: 1, id: '00dhw storage intern temperature' }, + { v: 'on', u: 0, id: '00dhw activated', c: 'wwactivated', l: ['off', 'on'] }, + { v: 'off', u: 0, id: '00dhw one time charging', c: 'wwonetime', l: ['off', 'on'] }, + { v: 'off', u: 0, id: '00dhw disinfecting', c: 'wwdisinfecting', l: ['off', 'on'] }, + { v: 'off', u: 0, id: '00dhw charging' }, + { v: 'off', u: 0, id: '00dhw recharging' }, + { v: 'on', u: 0, id: '00dhw temperature ok' }, + { v: 'off', u: 0, id: '00dhw active' }, + { v: 'on', u: 0, id: '00dhw 3way valve active' }, + { v: 0, u: 3, id: '00dhw set pump power' }, + { v: 288768, u: 0, id: '00dhw starts' }, + { v: 102151, u: 8, id: '00dhw active time' } + ] +}; + const emsesp_devicedata_99 = { data: [ { @@ -2026,7 +2026,7 @@ rest_server.get(LIST_NETWORKS_ENDPOINT, (req, res) => { res.json(list_networks); }); rest_server.get(SCAN_NETWORKS_ENDPOINT, (req, res) => { - res.sendStatus(202); + res.sendStatus(202); // reboot required }); // AP @@ -2100,6 +2100,7 @@ rest_server.get(VERIFY_AUTHORIZATION_ENDPOINT, (req, res) => { res.json(verify_authentication); }); rest_server.post(RESTART_ENDPOINT, (req, res) => { + console.log('command: restart'); res.sendStatus(200); }); rest_server.post(FACTORY_RESET_ENDPOINT, (req, res) => { @@ -2128,7 +2129,7 @@ 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(205).json(settings); // restart needed res.status(200).json(settings); // no restart needed }); rest_server.get(EMSESP_CORE_DATA_ENDPOINT, (req, res) => { @@ -2184,8 +2185,9 @@ rest_server.get(EMSESP_DEVICEDATA_ENDPOINT, (req, res) => { res.end(null, 'binary'); }); -rest_server.post(EMSESP_DEVICEENTITIES_ENDPOINT, (req, res) => { - const id = req.body.id; +rest_server.get(EMSESP_DEVICEENTITIES_ENDPOINT, (req, res) => { + const id = Number(req.query.id); + console.log('deviceentities for device ' + id + ' received'); let data = null; if (id === 1) { @@ -2233,6 +2235,7 @@ function updateMask(entity, de, dd) { } // find in dd, either looking for fullname or custom name + // console.log('looking for ' + fullname + ' in ' + dd.data); dd_objIndex = dd.data.findIndex((obj) => obj.id.slice(2) === fullname); if (dd_objIndex !== -1) { let changed = new Boolean(false); @@ -2432,10 +2435,12 @@ rest_server.post(EMSESP_WRITE_ANALOG_ENDPOINT, (req, res) => { res.sendStatus(200); }); -rest_server.post(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => { - const board_profile = req.body.board_profile; +rest_server.get(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => { + const board_profile = req.query.boardProfile; + // default values const data = { + board_profile: board_profile, led_gpio: settings.led_gpio, dallas_gpio: settings.dallas_gpio, rx_gpio: settings.rx_gpio, @@ -2559,9 +2564,10 @@ rest_server.post(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => { data.eth_clock_mode = 0; } - console.log('boardProfile POST. Sending back, profile: ' + board_profile + ', ' + 'data: ' + JSON.stringify(data)); + console.log('boardProfile GET. Sending back, profile: ' + board_profile + ', ' + 'data: ' + JSON.stringify(data)); - res.send(data); + // res.sendStatus(400); // send back an error, for testing + res.json(data); }); // EMS-ESP API specific diff --git a/src/web/WebCustomizationService.cpp b/src/web/WebCustomizationService.cpp index 0b6233f08..2a1932778 100644 --- a/src/web/WebCustomizationService.cpp +++ b/src/web/WebCustomizationService.cpp @@ -33,10 +33,12 @@ WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * f , _fsPersistence(WebCustomization::read, WebCustomization::update, this, fs, EMSESP_CUSTOMIZATION_FILE) , _masked_entities_handler(CUSTOM_ENTITIES_PATH, securityManager->wrapCallback(std::bind(&WebCustomizationService::custom_entities, this, _1, _2), - AuthenticationPredicates::IS_AUTHENTICATED)) - , _device_entities_handler(DEVICE_ENTITIES_PATH, - securityManager->wrapCallback(std::bind(&WebCustomizationService::device_entities, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)) { + server->on(DEVICE_ENTITIES_PATH, + HTTP_GET, + securityManager->wrapRequest(std::bind(&WebCustomizationService::device_entities, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); + + server->on(DEVICES_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebCustomizationService::devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); @@ -49,10 +51,6 @@ WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * f _masked_entities_handler.setMaxContentLength(2048); _masked_entities_handler.setMaxJsonBufferSize(2048); server->addHandler(&_masked_entities_handler); - - _device_entities_handler.setMethod(HTTP_POST); - _device_entities_handler.setMaxContentLength(256); - server->addHandler(&_device_entities_handler); } // this creates the customization file, saving it to the FS @@ -165,7 +163,7 @@ StateUpdateResult WebCustomization::update(JsonObject & root, WebCustomization & void WebCustomizationService::reset_customization(AsyncWebServerRequest * request) { #ifndef EMSESP_STANDALONE if (LittleFS.remove(EMSESP_CUSTOMIZATION_FILE)) { - AsyncWebServerResponse * response = request->beginResponse(200); // OK + AsyncWebServerResponse * response = request->beginResponse(205); // restart needed request->send(response); EMSESP::system_.restart_requested(true); return; @@ -199,17 +197,21 @@ void WebCustomizationService::devices(AsyncWebServerRequest * request) { } // send back list of device entities -void WebCustomizationService::device_entities(AsyncWebServerRequest * request, JsonVariant & json) { - if (json.is()) { +void WebCustomizationService::device_entities(AsyncWebServerRequest * request) { + uint8_t id; + if (request->hasParam(F_(id))) { + 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(true, buffer); + while (!response->getSize()) { delete response; buffer -= 1024; response = new MsgpackAsyncJsonResponse(true, buffer); } for (const auto & emsdevice : EMSESP::emsdevices) { - if (emsdevice->unique_id() == json["id"]) { + if (emsdevice->unique_id() == id) { #ifndef EMSESP_STANDALONE JsonArray output = response->getRoot(); emsdevice->generate_values_web_customization(output); @@ -319,7 +321,7 @@ void WebCustomizationService::custom_entities(AsyncWebServerRequest * request, J } } - AsyncWebServerResponse * response = request->beginResponse(need_reboot ? 201 : 200); // OK + AsyncWebServerResponse * response = request->beginResponse(need_reboot ? 205 : 200); // reboot or just OK request->send(response); } diff --git a/src/web/WebCustomizationService.h b/src/web/WebCustomizationService.h index 7e977959a..85435496c 100644 --- a/src/web/WebCustomizationService.h +++ b/src/web/WebCustomizationService.h @@ -24,9 +24,9 @@ // GET #define DEVICES_SERVICE_PATH "/rest/devices" #define EMSESP_CUSTOMIZATION_SERVICE_PATH "/rest/customization" +#define DEVICE_ENTITIES_PATH "/rest/deviceEntities" // POST -#define DEVICE_ENTITIES_PATH "/rest/deviceEntities" #define CUSTOM_ENTITIES_PATH "/rest/customEntities" #define RESET_CUSTOMIZATION_SERVICE_PATH "/rest/resetCustomizations" @@ -91,13 +91,13 @@ class WebCustomizationService : public StatefulService { // GET void devices(AsyncWebServerRequest * request); + void device_entities(AsyncWebServerRequest * request); // POST void custom_entities(AsyncWebServerRequest * request, JsonVariant & json); - void device_entities(AsyncWebServerRequest * request, JsonVariant & json); - void reset_customization(AsyncWebServerRequest * request); + void reset_customization(AsyncWebServerRequest * request); // command - AsyncCallbackJsonWebHandler _masked_entities_handler, _device_entities_handler; + AsyncCallbackJsonWebHandler _masked_entities_handler; }; } // namespace emsesp diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index 85e6a0353..77ec4566b 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -327,6 +327,7 @@ void WebDataService::write_temperature_sensor(AsyncWebServerRequest * request, J if (EMSESP::system_.fahrenheit()) { offset10 = offset / 0.18; } + ok = EMSESP::temperaturesensor_.update(id, name, offset10); } diff --git a/src/web/WebSettingsService.cpp b/src/web/WebSettingsService.cpp index 759d47a32..51d0537cc 100644 --- a/src/web/WebSettingsService.cpp +++ b/src/web/WebSettingsService.cpp @@ -26,12 +26,11 @@ using namespace std::placeholders; // for `_1` etc WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) : _httpEndpoint(WebSettings::read, WebSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager) - , _fsPersistence(WebSettings::read, WebSettings::update, this, fs, EMSESP_SETTINGS_FILE) - , _boardProfileHandler(EMSESP_BOARD_PROFILE_SERVICE_PATH, - securityManager->wrapCallback(std::bind(&WebSettingsService::board_profile, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) { - _boardProfileHandler.setMethod(HTTP_POST); - _boardProfileHandler.setMaxContentLength(256); - server->addHandler(&_boardProfileHandler); + , _fsPersistence(WebSettings::read, WebSettings::update, this, fs, EMSESP_SETTINGS_FILE) { + // GET + server->on(EMSESP_BOARD_PROFILE_SERVICE_PATH, + HTTP_GET, + securityManager->wrapRequest(std::bind(&WebSettingsService::board_profile, this, _1), AuthenticationPredicates::IS_ADMIN)); addUpdateHandler([&](const String & originId) { onUpdate(); }, false); } @@ -342,29 +341,29 @@ void WebSettingsService::save() { } // build the json profile to send back -void WebSettingsService::board_profile(AsyncWebServerRequest * request, JsonVariant & json) { - if (json.is()) { +void WebSettingsService::board_profile(AsyncWebServerRequest * request) { + if (request->hasParam("boardProfile")) { + std::string board_profile = request->getParam("boardProfile")->value().c_str(); + auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_MEDIUM); JsonObject root = response->getRoot(); - if (json.containsKey("board_profile")) { - String board_profile = json["board_profile"]; - std::vector data; // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode - (void)System::load_board_profile(data, board_profile.c_str()); - root["led_gpio"] = data[0]; - root["dallas_gpio"] = data[1]; - root["rx_gpio"] = data[2]; - root["tx_gpio"] = data[3]; - root["pbutton_gpio"] = data[4]; - root["phy_type"] = data[5]; - root["eth_power"] = data[6]; - root["eth_phy_addr"] = data[7]; - root["eth_clock_mode"] = data[8]; + std::vector data; // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode + (void)System::load_board_profile(data, board_profile); + root["board_profile"] = board_profile; + root["led_gpio"] = data[0]; + root["dallas_gpio"] = data[1]; + root["rx_gpio"] = data[2]; + root["tx_gpio"] = data[3]; + root["pbutton_gpio"] = data[4]; + root["phy_type"] = data[5]; + root["eth_power"] = data[6]; + root["eth_phy_addr"] = data[7]; + root["eth_clock_mode"] = data[8]; - response->setLength(); - request->send(response); - return; - } + response->setLength(); + request->send(response); + return; } AsyncWebServerResponse * response = request->beginResponse(200); diff --git a/src/web/WebSettingsService.h b/src/web/WebSettingsService.h index 61af4161b..0e892328e 100644 --- a/src/web/WebSettingsService.h +++ b/src/web/WebSettingsService.h @@ -122,11 +122,10 @@ class WebSettingsService : public StatefulService { void save(); private: - HttpEndpoint _httpEndpoint; - FSPersistence _fsPersistence; - AsyncCallbackJsonWebHandler _boardProfileHandler; + HttpEndpoint _httpEndpoint; + FSPersistence _fsPersistence; - void board_profile(AsyncWebServerRequest * request, JsonVariant & json); + void board_profile(AsyncWebServerRequest * request); void onUpdate(); }; From adc4760b5fdcf444ed1a0a6d5b6c1e76953d94b0 Mon Sep 17 00:00:00 2001 From: proddy Date: Wed, 14 Jun 2023 23:30:52 +0200 Subject: [PATCH 09/30] alova - implementing UpdateState --- interface/package.json | 2 +- interface/src/api/ap.ts | 3 +- interface/src/api/endpoints.ts | 8 +- interface/src/framework/ap/APStatusForm.tsx | 2 +- .../framework/system/GeneralFileUpload.tsx | 80 ++++++------ interface/src/project/DashboardStatus.tsx | 9 -- interface/src/project/HelpInformation.tsx | 35 +++--- .../src/project/SettingsCustomization.tsx | 6 +- .../project/SettingsCustomizationDialog.tsx | 2 +- interface/src/project/SettingsScheduler.tsx | 119 ++++++++---------- .../src/project/SettingsSchedulerDialog.tsx | 2 +- interface/src/project/api.ts | 101 ++++++++------- interface/src/project/types.ts | 6 +- interface/src/project/validators.ts | 2 + interface/src/utils/useRest2.ts | 8 +- interface/yarn.lock | 20 +-- mock-api/server.js | 34 +++-- 17 files changed, 202 insertions(+), 237 deletions(-) diff --git a/interface/package.json b/interface/package.json index dc09f548f..5c4fb7ae5 100644 --- a/interface/package.json +++ b/interface/package.json @@ -40,7 +40,7 @@ "react-dom": "latest", "react-dropzone": "^14.2.3", "react-icons": "^4.9.0", - "react-router-dom": "^6.12.1", + "react-router-dom": "^6.13.0", "react-toastify": "^9.1.3", "sockette": "^2.0.6", "typesafe-i18n": "^5.24.3", diff --git a/interface/src/api/ap.ts b/interface/src/api/ap.ts index f54ad1f59..a314227c2 100644 --- a/interface/src/api/ap.ts +++ b/interface/src/api/ap.ts @@ -5,11 +5,10 @@ import type { APSettings, APStatus } from 'types'; export const readAPStatus = () => alovaInstance.Get('/apStatus'); -// TODO change AXIOS to Alova +// TODO change APSettings AXIOS to Alova export function readAPSettings(): AxiosPromise { return AXIOS.get('/apSettings'); } - export function updateAPSettings(apSettings: APSettings): AxiosPromise { return AXIOS.post('/apSettings', apSettings); } diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index 3af7431db..987978963 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -6,9 +6,6 @@ import { unpack } from '../api/unpack'; import type { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios'; -export const WS_BASE_URL = '/ws/'; -export const ES_BASE_URL = '/es/'; - export const REST_BASE_URL = '/rest/'; export const API_BASE_URL = '/api/'; @@ -16,13 +13,14 @@ export const ACCESS_TOKEN = 'access_token'; const location = window.location; const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; +export const WS_BASE_URL = '/ws/'; +export const ES_BASE_URL = '/es/'; export const WEB_SOCKET_ROOT = webProtocol + '//' + location.host + WS_BASE_URL; export const EVENT_SOURCE_ROOT = location.protocol + '//' + location.host + ES_BASE_URL; export const alovaInstance = createAlova({ - baseURL: '/rest/', statesHook: ReactHook, - timeout: 50000, + timeout: 5000, requestAdapter: xhrRequestAdapter(), beforeRequest(method) { if (localStorage.getItem(ACCESS_TOKEN)) { diff --git a/interface/src/framework/ap/APStatusForm.tsx b/interface/src/framework/ap/APStatusForm.tsx index 611353513..ec754f796 100644 --- a/interface/src/framework/ap/APStatusForm.tsx +++ b/interface/src/framework/ap/APStatusForm.tsx @@ -28,7 +28,7 @@ export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => { }; const APStatusForm: FC = () => { - const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus()); + const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus); const { LL } = useI18nContext(); diff --git a/interface/src/framework/system/GeneralFileUpload.tsx b/interface/src/framework/system/GeneralFileUpload.tsx index 11ade4405..9c0a04e6c 100644 --- a/interface/src/framework/system/GeneralFileUpload.tsx +++ b/interface/src/framework/system/GeneralFileUpload.tsx @@ -1,5 +1,6 @@ import DownloadIcon from '@mui/icons-material/GetApp'; import { Typography, Button, Box } from '@mui/material'; +import { useRequest } from 'alova'; import { toast } from 'react-toastify'; import type { FileUploadConfig } from 'api/endpoints'; import type { AxiosPromise } from 'axios'; @@ -9,7 +10,6 @@ import { SingleUpload, useFileUpload } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import * as EMSESP from 'project/api'; -import { extractErrorMessage } from 'utils'; interface UploadFileProps { uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise; @@ -18,6 +18,19 @@ interface UploadFileProps { const GeneralFileUpload: FC = ({ uploadGeneralFile }) => { const [uploadFile, cancelUpload, uploading, uploadProgress, md5] = useFileUpload({ upload: uploadGeneralFile }); + 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 { LL } = useI18nContext(); const saveFile = (json: any, endpoint: string) => { @@ -35,56 +48,41 @@ const GeneralFileUpload: FC = ({ uploadGeneralFile }) => { toast.info(LL.DOWNLOAD_SUCCESSFUL()); }; + onSuccessGetSettings((event) => { + saveFile(event.data, 'settings'); + }); + onSuccessgetCustomizations((event) => { + saveFile(event.data, 'customizations'); + }); + onSuccessGetEntities((event) => { + saveFile(event.data, 'entities'); + }); + onSuccessGetSchedule((event) => { + saveFile(event.data, 'schedule'); + }); + const downloadSettings = async () => { - try { - const response = await EMSESP.getSettings(); - if (response.status !== 200) { - toast.error(LL.PROBLEM_LOADING()); - } else { - saveFile(response.data, 'settings'); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } + await getSettings().catch((error) => { + toast.error(error.message); + }); }; const downloadCustomizations = async () => { - try { - const response = await EMSESP.getCustomizations(); - if (response.status !== 200) { - toast.error(LL.PROBLEM_LOADING()); - } else { - saveFile(response.data, 'customizations'); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } + await getCustomizations().catch((error) => { + toast.error(error.message); + }); }; const downloadEntities = async () => { - try { - const response = await EMSESP.getEntities(); - if (response.status !== 200) { - toast.error(LL.PROBLEM_LOADING()); - } else { - saveFile(response.data, 'entities'); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } + await getEntities().catch((error) => { + toast.error(error.message); + }); }; const downloadSchedule = async () => { - try { - const response = await EMSESP.getSchedule(); - if (response.status !== 200) { - toast.error(LL.PROBLEM_LOADING()); - } else { - saveFile(response.data, 'schedule'); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } + await getSchedule().catch((error) => { + toast.error(error.message); + }); }; return ( diff --git a/interface/src/project/DashboardStatus.tsx b/interface/src/project/DashboardStatus.tsx index 9ef093b98..084c78820 100644 --- a/interface/src/project/DashboardStatus.tsx +++ b/interface/src/project/DashboardStatus.tsx @@ -170,15 +170,6 @@ const DashboardStatus: FC = () => { toast.error(err.message); }); setConfirmScan(false); - - // try { - // await EMSESP.scanDevices(); - // toast.info(LL.SCANNING() + '...'); - // } catch (error) { - // toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - // } finally { - // setConfirmScan(false); - // } }; const renderScanDialog = () => ( diff --git a/interface/src/project/HelpInformation.tsx b/interface/src/project/HelpInformation.tsx index e849ac19e..b85720508 100644 --- a/interface/src/project/HelpInformation.tsx +++ b/interface/src/project/HelpInformation.tsx @@ -4,6 +4,7 @@ import DownloadIcon from '@mui/icons-material/GetApp'; import GitHubIcon from '@mui/icons-material/GitHub'; import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone'; import { Typography, Button, Box, List, ListItem, ListItemText, Link, ListItemAvatar } from '@mui/material'; +import { useRequest } from 'alova'; import { toast } from 'react-toastify'; import * as EMSESP from './api'; import type { FC } from 'react'; @@ -11,16 +12,19 @@ import type { FC } from 'react'; import { SectionContent } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import { extractErrorMessage } from 'utils'; const HelpInformation: FC = () => { const { LL } = useI18nContext(); - const saveFile = (json: any, endpoint: string) => { + const { send: API, onSuccess: onSuccessAPI } = useRequest((data) => EMSESP.API(data), { + immediate: false + }); + + onSuccessAPI((event) => { const a = document.createElement('a'); - const filename = 'emsesp_' + endpoint + '.txt'; + const filename = 'emsesp_info.txt'; a.href = URL.createObjectURL( - new Blob([JSON.stringify(json, null, 2)], { + new Blob([JSON.stringify(event.data, null, 2)], { type: 'text/plain' }) ); @@ -29,23 +33,12 @@ const HelpInformation: FC = () => { a.click(); document.body.removeChild(a); toast.info(LL.DOWNLOAD_SUCCESSFUL()); - }; + }); - const callAPI = async (endpoint: string) => { - try { - const response = await EMSESP.API({ - device: 'system', - entity: endpoint, - id: 0 - }); - if (response.status !== 200) { - toast.error(LL.PROBLEM_LOADING()); - } else { - saveFile(response.data, endpoint); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } + const callAPI = async () => { + await API({ device: 'system', entity: 'info', id: 0 }).catch((error) => { + toast.error(error.message); + }); }; return ( @@ -96,7 +89,7 @@ const HelpInformation: FC = () => { size="small" variant="outlined" color="primary" - onClick={() => callAPI('info')} + onClick={() => callAPI()} > {LL.SUPPORT_INFO()} diff --git a/interface/src/project/SettingsCustomization.tsx b/interface/src/project/SettingsCustomization.tsx index 24fa60ecc..7883cf484 100644 --- a/interface/src/project/SettingsCustomization.tsx +++ b/interface/src/project/SettingsCustomization.tsx @@ -61,7 +61,7 @@ const SettingsCustomization: FC = () => { immediate: false }); - const { data: devices } = useRequest(EMSESP.readDevices()); + const { data: devices } = useRequest(EMSESP.readDevices); const { send: writeCustomEntities } = useRequest((data) => EMSESP.writeCustomEntities(data), { immediate: false }); @@ -236,8 +236,9 @@ const SettingsCustomization: FC = () => { }; const maskDisabled = (set: boolean) => { + // TODO fix update! updateDeviceEntities( - deviceEntities?.map(function (de) { + deviceEntities.map(function (de) { if ((de.m & selectedFilters || !selectedFilters) && de.id.toLowerCase().includes(search.toLowerCase())) { return { ...de, @@ -329,7 +330,6 @@ const SettingsCustomization: FC = () => { } } ); - // does this work or use onSuccess hook? setOriginalSettings(deviceEntities); } }; diff --git a/interface/src/project/SettingsCustomizationDialog.tsx b/interface/src/project/SettingsCustomizationDialog.tsx index 065a2dd00..50601785d 100644 --- a/interface/src/project/SettingsCustomizationDialog.tsx +++ b/interface/src/project/SettingsCustomizationDialog.tsx @@ -124,7 +124,7 @@ const SettingsCustomizationDialog = ({ open, onClose, onSave, selectedItem }: Se {LL.CANCEL()} diff --git a/interface/src/project/SettingsScheduler.tsx b/interface/src/project/SettingsScheduler.tsx index 59fdee05b..8c93252e4 100644 --- a/interface/src/project/SettingsScheduler.tsx +++ b/interface/src/project/SettingsScheduler.tsx @@ -6,6 +6,7 @@ import WarningIcon from '@mui/icons-material/Warning'; import { Box, Typography, Divider, Stack, Button } from '@mui/material'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; +import { updateState, useRequest } from 'alova'; import { useState, useEffect, useCallback } from 'react'; import { unstable_useBlocker as useBlocker } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -19,23 +20,30 @@ import type { FC } from 'react'; import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import { extractErrorMessage } from 'utils'; const SettingsScheduler: FC = () => { const { LL, locale } = useI18nContext(); const [numChanges, setNumChanges] = useState(0); const blocker = useBlocker(numChanges !== 0); - const [schedule, setSchedule] = useState([]); const [selectedScheduleItem, setSelectedScheduleItem] = useState(); const [dow, setDow] = useState([]); - const [errorMessage, setErrorMessage] = useState(); const [creating, setCreating] = useState(false); const [dialogOpen, setDialogOpen] = useState(false); + const { + data: schedule, + send: fetchSchedule, + error + } = useRequest(EMSESP.readSchedule, { + initialData: [] + }); + + const { send: writeSchedule } = useRequest((data) => EMSESP.writeSchedule(data), { immediate: false }); + function hasScheduleChanged(si: ScheduleItem) { return ( si.id !== si.o_id || - (si?.name || '') !== (si?.o_name || '') || + (si.name || '') !== (si.o_name || '') || si.active !== si.o_active || si.deleted !== si.o_deleted || si.flags !== si.o_flags || @@ -46,10 +54,13 @@ const SettingsScheduler: FC = () => { } useEffect(() => { - if (schedule) { - setNumChanges(schedule ? schedule.filter((si) => hasScheduleChanged(si)).length : 0); - } - }, [schedule]); + const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }); + const days = [1, 2, 3, 4, 5, 6, 7].map((day) => { + const dd = day < 10 ? `0${day}` : day; + return new Date(`2017-01-${dd}T00:00:00+00:00`); + }); + setDow(days.map((date) => formatter.format(date))); + }, [locale]); const schedule_theme = useTheme({ Table: ` @@ -96,63 +107,30 @@ const SettingsScheduler: FC = () => { ` }); - const fetchSchedule = useCallback(async () => { - try { - const response = await EMSESP.readSchedule(); - setSchedule( - response.data.schedule.map((si) => ({ - ...si, - o_id: si.id, - o_active: si.active, - o_deleted: si.deleted, - o_flags: si.flags, - o_time: si.time, - o_cmd: si.cmd, - o_value: si.value, - o_name: si.name - })) - ); - } catch (error) { - setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } - }, [LL]); - - useEffect(() => { - const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }); - const days = [1, 2, 3, 4, 5, 6, 7].map((day) => { - const dd = day < 10 ? `0${day}` : day; - return new Date(`2017-01-${dd}T00:00:00+00:00`); - }); - setDow(days.map((date) => formatter.format(date))); - void fetchSchedule(); - }, [locale, fetchSchedule]); - const saveSchedule = async () => { - if (schedule) { - try { - const response = await EMSESP.writeSchedule({ - schedule: schedule - .filter((si) => !si.deleted) - .map((condensed_si) => ({ - id: condensed_si.id, - active: condensed_si.active, - flags: condensed_si.flags, - time: condensed_si.time, - cmd: condensed_si.cmd, - value: condensed_si.value, - name: condensed_si.name - })) - }); - if (response.status === 200) { - toast.success(LL.SCHEDULE_UPDATED()); - } else { - toast.error(LL.PROBLEM_UPDATING()); - } + await writeSchedule( + schedule + .filter((si) => !si.deleted) + .map((condensed_si) => ({ + id: condensed_si.id, + active: condensed_si.active, + flags: condensed_si.flags, + time: condensed_si.time, + cmd: condensed_si.cmd, + value: condensed_si.value, + name: condensed_si.name + })) + ) + .then(() => { + toast.success(LL.SCHEDULE_UPDATED()); + }) + .catch((err) => { + toast.error(err.message); + }) + .finally(async () => { await fetchSchedule(); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } - } + setNumChanges(0); + }); }; const editScheduleItem = useCallback((si: ScheduleItem) => { @@ -167,11 +145,14 @@ const SettingsScheduler: FC = () => { const onDialogSave = (updatedItem: ScheduleItem) => { setDialogOpen(false); - if (schedule && creating) { - setSchedule([...schedule.filter((si) => creating || si.o_id !== updatedItem.o_id), updatedItem]); - } else { - setSchedule(schedule?.map((si) => (si.id === updatedItem.id ? { ...si, ...updatedItem } : si))); - } + + updateState('schedule', (data) => { + const new_data = creating + ? [...data.filter((si) => creating || si.o_id !== updatedItem.o_id), updatedItem] + : data.map((si) => (si.id === updatedItem.id ? { ...si, ...updatedItem } : si)); + setNumChanges(new_data.filter((si) => hasScheduleChanged(si)).length); + return new_data; + }); }; const addScheduleItem = () => { @@ -191,7 +172,7 @@ const SettingsScheduler: FC = () => { const renderSchedule = () => { if (!schedule) { - return ; + return ; } const dayBox = (si: ScheduleItem, flag: number) => ( diff --git a/interface/src/project/SettingsSchedulerDialog.tsx b/interface/src/project/SettingsSchedulerDialog.tsx index 49870a4d0..d074848a4 100644 --- a/interface/src/project/SettingsSchedulerDialog.tsx +++ b/interface/src/project/SettingsSchedulerDialog.tsx @@ -215,7 +215,7 @@ const SettingsSchedulerDialog = ({ /> alovaInstance.Get(`/coreData`); +export const readCoreData = () => alovaInstance.Get(`/rest/coreData`); export const readDeviceData = (id: number) => - alovaInstance.Get('/deviceData', { + alovaInstance.Get('/rest/deviceData', { params: { id }, responseType: 'arraybuffer' // uses msgpack }); -export const writeDeviceValue = (data: any) => alovaInstance.Post('/writeDeviceValue', data); +export const writeDeviceValue = (data: any) => alovaInstance.Post('/rest/writeDeviceValue', data); // SettingsApplication -export const readSettings = () => alovaInstance.Get('/settings'); -export const writeSettings = (data: any) => alovaInstance.Post('/settings', data); +export const readSettings = () => alovaInstance.Get('/rest/settings'); +export const writeSettings = (data: any) => alovaInstance.Post('/rest/settings', data); export const getBoardProfile = (boardProfile: string) => - alovaInstance.Get('/boardProfile', { + alovaInstance.Get('/rest/boardProfile', { params: { boardProfile } }); -export const restart = () => alovaInstance.Post('/restart'); +export const restart = () => alovaInstance.Post('/rest/restart'); // SettingsCustomization export const readDeviceEntities = (id: number) => - alovaInstance.Get('/deviceEntities', { + alovaInstance.Get('/rest/deviceEntities', { params: { id }, responseType: 'arraybuffer', - transformData(rawData: any) { - return rawData.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })); + transformData(data: any) { + return data.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })); } }); -export const readDevices = () => alovaInstance.Get('/devices'); -export const resetCustomizations = () => alovaInstance.Post('/resetCustomizations'); -export const writeCustomEntities = (data: any) => alovaInstance.Post('/customEntities', data); +export const readDevices = () => alovaInstance.Get('/rest/devices'); +export const resetCustomizations = () => alovaInstance.Post('/rest/resetCustomizations'); +export const writeCustomEntities = (data: any) => alovaInstance.Post('/rest/customEntities', data); // DashboardSensors -export const readSensorData = () => alovaInstance.Get('/sensorData'); -export const writeTemperatureSensor = (ts: WriteTemperatureSensor) => alovaInstance.Post('/writeTemperatureSensor', ts); -export const writeAnalogSensor = (as: WriteAnalogSensor) => alovaInstance.Post('/writeAnalogSensor', as); - -// TODO think about naming, get... and not get etc... +export const readSensorData = () => alovaInstance.Get('/rest/sensorData'); +export const writeTemperatureSensor = (ts: WriteTemperatureSensor) => + alovaInstance.Post('/rest/writeTemperatureSensor', ts); +export const writeAnalogSensor = (as: WriteAnalogSensor) => alovaInstance.Post('/rest/writeAnalogSensor', as); // DashboardStatus -export const readStatus = () => alovaInstance.Get('/status'); -export const scanDevices = () => alovaInstance.Post('/scanDevices'); +export const readStatus = () => alovaInstance.Get('/rest/status'); +export const scanDevices = () => alovaInstance.Post('/rest/scanDevices'); -// ALOVA goes here.... +// HelpInformation +export const API = (apiCall: APIcall) => alovaInstance.Post('/api', apiCall); -export function API(apiCall: APIcall): AxiosPromise { - return AXIOS_API.post('/', apiCall); // command -} +// GeneralFileUpload +export const getSettings = () => alovaInstance.Get('/rest/getSettings'); +export const getCustomizations = () => alovaInstance.Get('/rest/getCustomizations'); +export const getEntities = () => alovaInstance.Get('/rest/getEntities'); +export const getSchedule = () => alovaInstance.Get('/rest/getSchedule'); -export function getSettings(): AxiosPromise { - return AXIOS.get('/getSettings'); -} - -export function getCustomizations(): AxiosPromise { - return AXIOS.get('/getCustomizations'); -} - -export function getSchedule(): AxiosPromise { - return AXIOS.get('/getSchedule'); -} - -export function readSchedule(): AxiosPromise { - return AXIOS.get('/schedule'); -} - -export function writeSchedule(schedule: Schedule): AxiosPromise { - return AXIOS.post('/schedule', schedule); -} - -export function getEntities(): AxiosPromise { - return AXIOS.get('/getEntities'); -} +// SettingsScheduler +export const readSchedule = () => + alovaInstance.Get('/rest/schedule', { + name: 'schedule', + transformData(data: any) { + return data.map((si: ScheduleItem) => ({ + ...si, + o_id: si.id, + o_active: si.active, + o_deleted: si.deleted, + o_flags: si.flags, + o_time: si.time, + o_cmd: si.cmd, + o_value: si.value, + o_name: si.name + })); + } + }); +export const writeSchedule = (data: any) => alovaInstance.Post('/rest/schedule', data); +// SettingsCustomization +// TODO move last Entities* to Alova export function readEntities(): AxiosPromise { return AXIOS.get('/entities'); } - export function writeEntities(entities: Entities): AxiosPromise { return AXIOS.post('/entities', entities); } diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index a50b0281c..bdbbbd167 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -292,7 +292,7 @@ export interface ScheduleItem { time: string; cmd: string; value: string; - name?: string; // optional + name: string; // optional o_id?: number; o_active?: boolean; o_deleted?: boolean; @@ -303,10 +303,6 @@ export interface ScheduleItem { o_name?: string; } -export interface Schedule { - schedule: ScheduleItem[]; -} - export enum ScheduleFlag { SCHEDULE_SUN = 1, SCHEDULE_MON = 2, diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index 32b4f2505..bc7147a1d 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -90,6 +90,8 @@ export const schedulerItemValidation = () => new Schema({ name: [ { + // TODO name must be unique - add check + required: true, type: 'string', pattern: /^[a-zA-Z0-9_\\.]{0,15}$/, message: "Must be <15 characters: alpha numeric, '_' or '.'" diff --git a/interface/src/utils/useRest2.ts b/interface/src/utils/useRest2.ts index 7e8688da0..b71b1c9cf 100644 --- a/interface/src/utils/useRest2.ts +++ b/interface/src/utils/useRest2.ts @@ -3,15 +3,11 @@ 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 RestRequestOptions2 { - read: () => Method, AlovaXHRResponseHeaders>; - update: ( - value: D - ) => Method, AlovaXHRResponseHeaders>; + read: () => Method; + update: (value: D) => Method; } // TODO rename back to useRest diff --git a/interface/yarn.lock b/interface/yarn.lock index ca56b2b5d..6803c97b3 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -1567,7 +1567,7 @@ __metadata: react-dom: latest react-dropzone: ^14.2.3 react-icons: ^4.9.0 - react-router-dom: ^6.12.1 + react-router-dom: ^6.13.0 react-toastify: ^9.1.3 rollup-plugin-visualizer: ^5.9.2 sockette: ^2.0.6 @@ -4807,27 +4807,27 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^6.12.1": - version: 6.12.1 - resolution: "react-router-dom@npm:6.12.1" +"react-router-dom@npm:^6.13.0": + version: 6.13.0 + resolution: "react-router-dom@npm:6.13.0" dependencies: "@remix-run/router": 1.6.3 - react-router: 6.12.1 + react-router: 6.13.0 peerDependencies: react: ">=16.8" react-dom: ">=16.8" - checksum: 4de0d4159a9dd2de0477d7608e9055262ebdd5dc41fc918b44b38170cc8ed407fa7dbb73bdb85e9469614502ad4772523b8a7f32c2609e62973feb41b70d871b + checksum: 760c57063e305b0623f7b39bca3c7f5c169af557867eac7d1b19419111f7b6a9ed95d2083305c40ce98fd3be27e82c61f5674c90dd066a67653cb15901d00991 languageName: node linkType: hard -"react-router@npm:6.12.1": - version: 6.12.1 - resolution: "react-router@npm:6.12.1" +"react-router@npm:6.13.0": + version: 6.13.0 + resolution: "react-router@npm:6.13.0" dependencies: "@remix-run/router": 1.6.3 peerDependencies: react: ">=16.8" - checksum: 33a39eca122f3519f1aa91d8e6585fab669b3b06b621aa624f7db1ffda6b84676facd57028652f2fba7325a208abef906a5ba6c91d55d5a5d29993583d82dd61 + checksum: c58b4b943d3a76d328c6ef299567a85d84a69b417f5f5d07596b0b9bd40632a828591d6f470b0f233ff9095f489f9fd5c8666fb03513e9470927447169a91d8a languageName: node linkType: hard diff --git a/mock-api/server.js b/mock-api/server.js index c805f7bc6..3b42ce97a 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -1798,8 +1798,9 @@ let emsesp_entities = { }; // SCHEDULE -let emsesp_schedule = { - schedule: [ +let emsesp_schedule = + // schedule: [ + [ { id: 1, active: true, @@ -1836,11 +1837,9 @@ let emsesp_schedule = { value: '', name: 'auto_restart' } - ] -}; + ]; // CUSTOMIZATIONS - const emsesp_deviceentities_1 = [{}]; const emsesp_deviceentities_3 = [{}]; const emsesp_deviceentities_5 = [{}]; @@ -2327,7 +2326,7 @@ rest_server.post(EMSESP_CUSTOM_ENTITIES_ENDPOINT, (req, res) => { rest_server.post(EMSESP_WRITE_SCHEDULE_ENDPOINT, (req, res) => { console.log('write schedule'); - console.log(req.body.schedule); + console.log(req.body); emsesp_schedule = req.body; res.sendStatus(200); }); @@ -2625,6 +2624,7 @@ rest_server.post(API_ENDPOINT_ROOT, (req, res) => { if (req.body.device === 'system') { if (req.body.entity === 'info') { console.log('sending system info: ' + JSON.stringify(emsesp_info)); + res.json(emsesp_info); } else if (req.body.entity === 'settings') { console.log('sending system settings: ' + JSON.stringify(settings)); res.json(settings); @@ -2652,25 +2652,37 @@ rest_server.get(SYSTEM_INFO_ENDPOINT, (req, res) => { const GET_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'getSettings'; rest_server.get(GET_SETTINGS_ENDPOINT, (req, res) => { - console.log('System Settings:'); + console.log('getSettings:'); res.json(settings); }); const GET_CUSTOMIZATIONS_ENDPOINT = REST_ENDPOINT_ROOT + 'getCustomizations'; rest_server.get(GET_CUSTOMIZATIONS_ENDPOINT, (req, res) => { - console.log('Customization'); + console.log('getCustomization'); // not implemented yet res.sendStatus(200); }); -const GET_SCHEDULE_ENDPOINT = REST_ENDPOINT_ROOT + 'schedule'; +const GET_ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'getEntities'; +rest_server.get(GET_ENTITIES_ENDPOINT, (req, res) => { + console.log('getEntities'); + res.json(emsesp_entities); +}); + +const GET_SCHEDULE_ENDPOINT = REST_ENDPOINT_ROOT + 'getSchedule'; rest_server.get(GET_SCHEDULE_ENDPOINT, (req, res) => { + console.log('getSchedule'); + res.json(emsesp_schedule); +}); + +const SCHEDULE_ENDPOINT = REST_ENDPOINT_ROOT + 'schedule'; +rest_server.get(SCHEDULE_ENDPOINT, (req, res) => { console.log('Sending Schedule data'); res.json(emsesp_schedule); }); -const GET_ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'entities'; -rest_server.get(GET_ENTITIES_ENDPOINT, (req, res) => { +const ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'entities'; +rest_server.get(ENTITIES_ENDPOINT, (req, res) => { console.log('Sending Entities data'); res.json(emsesp_entities); }); From f58dbf6ec1091f6f316e453c7adc09feb5364f40 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 15 Jun 2023 16:08:25 +0200 Subject: [PATCH 10/30] alova - update comments --- interface/src/AuthenticatedRouting.tsx | 1 + interface/src/api/authentication.ts | 2 +- interface/src/api/endpoints.ts | 13 +- interface/src/api/features.ts | 1 + interface/src/api/mqtt.ts | 1 + interface/src/api/network.ts | 1 + interface/src/api/ntp.ts | 1 + interface/src/api/security.ts | 1 + interface/src/api/system.ts | 1 + .../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 +- .../src/project/SettingsCustomization.tsx | 9 +- interface/src/project/SettingsEntities.tsx | 115 +++++++----------- interface/src/project/api.ts | 59 +++++---- interface/src/project/validators.ts | 2 +- interface/src/utils/binding.ts | 6 - mock-api/server.js | 31 +++-- 20 files changed, 117 insertions(+), 137 deletions(-) diff --git a/interface/src/AuthenticatedRouting.tsx b/interface/src/AuthenticatedRouting.tsx index eaf6041f2..7474821c8 100644 --- a/interface/src/AuthenticatedRouting.tsx +++ b/interface/src/AuthenticatedRouting.tsx @@ -33,6 +33,7 @@ const AuthenticatedRouting: FC = () => { ); useEffect(() => { + // TODO how to replace AXIOS.interceptors.response.use ??? const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError); return () => AXIOS.interceptors.response.eject(axiosHandlerId); }, [handleApiResponseError]); diff --git a/interface/src/api/authentication.ts b/interface/src/api/authentication.ts index 9b8b01030..d66042236 100644 --- a/interface/src/api/authentication.ts +++ b/interface/src/api/authentication.ts @@ -9,10 +9,10 @@ import type { Me, SignInRequest, SignInResponse } from 'types'; export const SIGN_IN_PATHNAME = 'loginPathname'; export const SIGN_IN_SEARCH = 'loginSearch'; +// TODO move to Alova export function verifyAuthorization(): AxiosPromise { return AXIOS.get('/verifyAuthorization'); } - export function signIn(request: SignInRequest): AxiosPromise { return AXIOS.post('/signIn', request); } diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index 987978963..cd1b4b574 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -4,23 +4,20 @@ import ReactHook from 'alova/react'; import axios from 'axios'; import { unpack } from '../api/unpack'; +// TODO axios can be removed import type { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios'; - export const REST_BASE_URL = '/rest/'; export const API_BASE_URL = '/api/'; export const ACCESS_TOKEN = 'access_token'; -const location = window.location; -const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; -export const WS_BASE_URL = '/ws/'; -export const ES_BASE_URL = '/es/'; -export const WEB_SOCKET_ROOT = webProtocol + '//' + location.host + WS_BASE_URL; -export const EVENT_SOURCE_ROOT = location.protocol + '//' + location.host + ES_BASE_URL; +const host = window.location.host; +export const WEB_SOCKET_ROOT = 'ws://' + host + '/ws/'; +export const EVENT_SOURCE_ROOT = 'http://' + host + '/es/'; export const alovaInstance = createAlova({ statesHook: ReactHook, - timeout: 5000, + timeout: 3000, requestAdapter: xhrRequestAdapter(), beforeRequest(method) { if (localStorage.getItem(ACCESS_TOKEN)) { diff --git a/interface/src/api/features.ts b/interface/src/api/features.ts index 6ada2405c..6d8b663fa 100644 --- a/interface/src/api/features.ts +++ b/interface/src/api/features.ts @@ -3,6 +3,7 @@ import type { AxiosPromise } from 'axios'; import type { Features } from 'types'; +// TODO move to Alova export function readFeatures(): AxiosPromise { return AXIOS.get('/features'); } diff --git a/interface/src/api/mqtt.ts b/interface/src/api/mqtt.ts index d599a3121..a2668193b 100644 --- a/interface/src/api/mqtt.ts +++ b/interface/src/api/mqtt.ts @@ -2,6 +2,7 @@ import { AXIOS } from './endpoints'; import type { AxiosPromise } from 'axios'; import type { MqttSettings, MqttStatus } from 'types'; +// TODO move to alova export function readMqttStatus(): AxiosPromise { return AXIOS.get('/mqttStatus'); } diff --git a/interface/src/api/network.ts b/interface/src/api/network.ts index a9535336c..2c50bfdf2 100644 --- a/interface/src/api/network.ts +++ b/interface/src/api/network.ts @@ -3,6 +3,7 @@ import type { AxiosPromise } from 'axios'; import type { WiFiNetworkList, NetworkSettings, NetworkStatus } from 'types'; +// TODO move to alova export function readNetworkStatus(): AxiosPromise { return AXIOS.get('/networkStatus'); } diff --git a/interface/src/api/ntp.ts b/interface/src/api/ntp.ts index dcd3c7ca9..c0cd94283 100644 --- a/interface/src/api/ntp.ts +++ b/interface/src/api/ntp.ts @@ -2,6 +2,7 @@ import { AXIOS } from './endpoints'; import type { AxiosPromise } from 'axios'; import type { NTPSettings, NTPStatus, Time } from 'types'; +// TODO move to Alova export function readNTPStatus(): AxiosPromise { return AXIOS.get('/ntpStatus'); } diff --git a/interface/src/api/security.ts b/interface/src/api/security.ts index f46194432..1fecb6611 100644 --- a/interface/src/api/security.ts +++ b/interface/src/api/security.ts @@ -3,6 +3,7 @@ import type { AxiosPromise } from 'axios'; import type { SecuritySettings, Token } from 'types'; +// TODO move to Alova export function readSecuritySettings(): AxiosPromise { return AXIOS.get('/securitySettings'); } diff --git a/interface/src/api/system.ts b/interface/src/api/system.ts index dc9b34db6..80bf6df1d 100644 --- a/interface/src/api/system.ts +++ b/interface/src/api/system.ts @@ -4,6 +4,7 @@ import type { AxiosPromise } from 'axios'; import type { OTASettings, SystemStatus, LogSettings, LogEntries } from 'types'; +// TODO move to Alova export function readSystemStatus(timeout?: number): AxiosPromise { return AXIOS.get('/systemStatus', { timeout }); } diff --git a/interface/src/framework/mqtt/MqttStatusForm.tsx b/interface/src/framework/mqtt/MqttStatusForm.tsx index 4c2daa4c6..dc914f7e7 100644 --- a/interface/src/framework/mqtt/MqttStatusForm.tsx +++ b/interface/src/framework/mqtt/MqttStatusForm.tsx @@ -38,7 +38,7 @@ export const mqttQueueHighlight = ({ mqtt_queued }: MqttStatus, theme: Theme) => }; const MqttStatusForm: FC = () => { - // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus()); + // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus); 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 5a720fbaf..6d0ca1b55 100644 --- a/interface/src/framework/network/NetworkStatusForm.tsx +++ b/interface/src/framework/network/NetworkStatusForm.tsx @@ -59,7 +59,7 @@ const IPs = (status: NetworkStatus) => { }; const NetworkStatusForm: FC = () => { - // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus()); + // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus); 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 971246c29..4b77adaf5 100644 --- a/interface/src/framework/ntp/NTPStatusForm.tsx +++ b/interface/src/framework/ntp/NTPStatusForm.tsx @@ -52,7 +52,7 @@ export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => { }; const NTPStatusForm: FC = () => { - // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus()); + // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus); const { loadData, data, errorMessage } = useRest({ read: NTPApi.readNTPStatus }); const [localTime, setLocalTime] = useState(''); diff --git a/interface/src/framework/system/SystemLog.tsx b/interface/src/framework/system/SystemLog.tsx index e7a1c761c..e9ef0d388 100644 --- a/interface/src/framework/system/SystemLog.tsx +++ b/interface/src/framework/system/SystemLog.tsx @@ -49,7 +49,7 @@ const levelLabel = (level: LogLevel) => { const SystemLog: FC = () => { const { LL } = useI18nContext(); - // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus()); + // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus); 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 719fd5a22..6d62e2101 100644 --- a/interface/src/framework/system/SystemStatusForm.tsx +++ b/interface/src/framework/system/SystemStatusForm.tsx @@ -53,7 +53,7 @@ const SystemStatusForm: FC = () => { const { LL } = useI18nContext(); const [restarting, setRestarting] = useState(); - // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus()); + // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus); const { loadData, data, errorMessage } = useRest({ read: SystemApi.readSystemStatus }); const { me } = useContext(AuthenticatedContext); diff --git a/interface/src/project/SettingsCustomization.tsx b/interface/src/project/SettingsCustomization.tsx index 7883cf484..eaf5e426d 100644 --- a/interface/src/project/SettingsCustomization.tsx +++ b/interface/src/project/SettingsCustomization.tsx @@ -65,11 +65,7 @@ const SettingsCustomization: FC = () => { const { send: writeCustomEntities } = useRequest((data) => EMSESP.writeCustomEntities(data), { immediate: false }); - const { - send: readDeviceEntities, - update: updateDeviceEntities, - onSuccess: onSuccess - } = useRequest((data) => EMSESP.readDeviceEntities(data), { + const { send: readDeviceEntities, onSuccess: onSuccess } = useRequest((data) => EMSESP.readDeviceEntities(data), { initialData: [], immediate: false, force: true @@ -236,8 +232,7 @@ const SettingsCustomization: FC = () => { }; const maskDisabled = (set: boolean) => { - // TODO fix update! - updateDeviceEntities( + setDeviceEntities( deviceEntities.map(function (de) { if ((de.m & selectedFilters || !selectedFilters) && de.id.toLowerCase().includes(search.toLowerCase())) { return { diff --git a/interface/src/project/SettingsEntities.tsx b/interface/src/project/SettingsEntities.tsx index daec44a35..ce1a1276e 100644 --- a/interface/src/project/SettingsEntities.tsx +++ b/interface/src/project/SettingsEntities.tsx @@ -4,7 +4,8 @@ import WarningIcon from '@mui/icons-material/Warning'; import { Button, Typography, Box } from '@mui/material'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; -import { useState, useEffect, useCallback } from 'react'; +import { updateState, useRequest } from 'alova'; +import { useState, useCallback } from 'react'; import { unstable_useBlocker as useBlocker } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -18,18 +19,25 @@ import type { FC } from 'react'; import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import { extractErrorMessage } from 'utils'; const SettingsEntities: FC = () => { const { LL } = useI18nContext(); const [numChanges, setNumChanges] = useState(0); const blocker = useBlocker(numChanges !== 0); - const [entities, setEntities] = useState(); const [selectedEntityItem, setSelectedEntityItem] = useState(); - const [errorMessage, setErrorMessage] = useState(); const [creating, setCreating] = useState(false); const [dialogOpen, setDialogOpen] = useState(false); + const { + data: entities, + send: fetchEntities, + error + } = useRequest(EMSESP.readEntities, { + initialData: [] + }); + + const { send: writeEntities } = useRequest((data) => EMSESP.writeEntities(data), { immediate: false }); + function hasEntityChanged(ei: EntityItem) { return ( ei.id !== ei.o_id || @@ -45,12 +53,6 @@ const SettingsEntities: FC = () => { ); } - useEffect(() => { - if (entities) { - setNumChanges(entities ? entities.filter((ei) => hasEntityChanged(ei)).length : 0); - } - }, [entities]); - const entity_theme = useTheme({ Table: ` --data-table-library_grid-template-columns: repeat(1, minmax(60px, 1fr)) minmax(80px, auto) 80px 80px 80px; @@ -105,62 +107,32 @@ const SettingsEntities: FC = () => { ` }); - const fetchEntities = useCallback(async () => { - try { - const response = await EMSESP.readEntities(); - setEntities( - response.data.entities.map((ei) => ({ - ...ei, - o_id: ei.id, - o_device_id: ei.device_id, - o_type_id: ei.type_id, - o_offset: ei.offset, - o_factor: ei.factor, - o_uom: ei.uom, - o_value_type: ei.value_type, - o_name: ei.name, - o_writeable: ei.writeable, - o_deleted: ei.deleted - })) - ); - } catch (error) { - setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } - }, [LL]); - - useEffect(() => { - void fetchEntities(); - }, [fetchEntities]); - const saveEntities = async () => { - if (entities) { - try { - const response = await EMSESP.writeEntities({ - entities: entities - .filter((ei) => !ei.deleted) - .map((condensed_ei) => ({ - id: condensed_ei.id, - name: condensed_ei.name, - device_id: condensed_ei.device_id, - type_id: condensed_ei.type_id, - offset: condensed_ei.offset, - factor: condensed_ei.factor, - uom: condensed_ei.uom, - writeable: condensed_ei.writeable, - value_type: condensed_ei.value_type - })) - }); - - if (response.status === 200) { - toast.success(LL.ENTITIES_UPDATED()); - } else { - toast.error(LL.PROBLEM_UPDATING()); - } + await writeEntities( + entities + .filter((ei) => !ei.deleted) + .map((condensed_ei) => ({ + id: condensed_ei.id, + name: condensed_ei.name, + device_id: condensed_ei.device_id, + type_id: condensed_ei.type_id, + offset: condensed_ei.offset, + factor: condensed_ei.factor, + uom: condensed_ei.uom, + writeable: condensed_ei.writeable, + value_type: condensed_ei.value_type + })) + ) + .then(() => { + toast.success(LL.ENTITIES_UPDATED()); + }) + .catch((err) => { + toast.error(err.message); + }) + .finally(async () => { await fetchEntities(); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } - } + setNumChanges(0); + }); }; const editEntityItem = useCallback((ei: EntityItem) => { @@ -175,11 +147,14 @@ const SettingsEntities: FC = () => { const onDialogSave = (updatedItem: EntityItem) => { setDialogOpen(false); - if (entities && creating) { - setEntities([...entities.filter((ei) => creating || ei.o_id !== updatedItem.o_id), updatedItem]); - } else { - setEntities(entities?.map((ei) => (ei.id === updatedItem.id ? { ...ei, ...updatedItem } : ei))); - } + + updateState('entities', (data) => { + const new_data = creating + ? [...data.filter((ei) => creating || ei.o_id !== updatedItem.o_id), updatedItem] + : data.map((ei) => (ei.id === updatedItem.id ? { ...ei, ...updatedItem } : ei)); + setNumChanges(new_data.filter((ei) => hasEntityChanged(ei)).length); + return new_data; + }); }; const addEntityItem = () => { @@ -215,7 +190,7 @@ const SettingsEntities: FC = () => { const renderEntity = () => { if (!entities) { - return ; + return ; } return ( diff --git a/interface/src/project/api.ts b/interface/src/project/api.ts index 2476090d3..3d872829a 100644 --- a/interface/src/project/api.ts +++ b/interface/src/project/api.ts @@ -10,10 +10,10 @@ import type { SensorData, Entities, DeviceData, - ScheduleItem + ScheduleItem, + EntityItem } from './types'; -import type { AxiosPromise } from 'axios'; -import { AXIOS, alovaInstance } from 'api/endpoints'; +import { alovaInstance } from 'api/endpoints'; // DashboardDevices export const readCoreData = () => alovaInstance.Get(`/rest/coreData`); @@ -33,19 +33,6 @@ export const getBoardProfile = (boardProfile: string) => }); export const restart = () => alovaInstance.Post('/rest/restart'); -// SettingsCustomization -export const readDeviceEntities = (id: number) => - alovaInstance.Get('/rest/deviceEntities', { - params: { id }, - responseType: 'arraybuffer', - transformData(data: any) { - return data.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })); - } - }); -export const readDevices = () => alovaInstance.Get('/rest/devices'); -export const resetCustomizations = () => alovaInstance.Post('/rest/resetCustomizations'); -export const writeCustomEntities = (data: any) => alovaInstance.Post('/rest/customEntities', data); - // DashboardSensors export const readSensorData = () => alovaInstance.Get('/rest/sensorData'); export const writeTemperatureSensor = (ts: WriteTemperatureSensor) => @@ -65,6 +52,19 @@ export const getCustomizations = () => alovaInstance.Get('/rest/getCustomization export const getEntities = () => alovaInstance.Get('/rest/getEntities'); export const getSchedule = () => alovaInstance.Get('/rest/getSchedule'); +// SettingsCustomization +export const readDeviceEntities = (id: number) => + alovaInstance.Get('/rest/deviceEntities', { + params: { id }, + responseType: 'arraybuffer', + transformData(data: any) { + return data.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })); + } + }); +export const readDevices = () => alovaInstance.Get('/rest/devices'); +export const resetCustomizations = () => alovaInstance.Post('/rest/resetCustomizations'); +export const writeCustomEntities = (data: any) => alovaInstance.Post('/rest/customEntities', data); + // SettingsScheduler export const readSchedule = () => alovaInstance.Get('/rest/schedule', { @@ -86,10 +86,23 @@ export const readSchedule = () => export const writeSchedule = (data: any) => alovaInstance.Post('/rest/schedule', data); // SettingsCustomization -// TODO move last Entities* to Alova -export function readEntities(): AxiosPromise { - return AXIOS.get('/entities'); -} -export function writeEntities(entities: Entities): AxiosPromise { - return AXIOS.post('/entities', entities); -} +export const readEntities = () => + alovaInstance.Get('/rest/entities', { + name: 'entities', + transformData(data: any) { + return data.map((ei: EntityItem) => ({ + ...ei, + o_id: ei.id, + o_device_id: ei.device_id, + o_type_id: ei.type_id, + o_offset: ei.offset, + o_factor: ei.factor, + o_uom: ei.uom, + o_value_type: ei.value_type, + o_name: ei.name, + o_writeable: ei.writeable, + o_deleted: ei.deleted + })); + } + }); +export const writeEntities = (data: any) => alovaInstance.Post('/rest/entities', data); diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index bc7147a1d..02724df3a 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -90,7 +90,7 @@ export const schedulerItemValidation = () => new Schema({ name: [ { - // TODO name must be unique - add check + // TODO validator: add check for unique name required: true, type: 'string', pattern: /^[a-zA-Z0-9_\\.]{0,15}$/, diff --git a/interface/src/utils/binding.ts b/interface/src/utils/binding.ts index 74a755a52..3586f40f7 100644 --- a/interface/src/utils/binding.ts +++ b/interface/src/utils/binding.ts @@ -28,7 +28,6 @@ export const updateValueDirty = const updated_value = extractEventValue(event); const name = event.target.name; - // TODO not sure how this is even working!! updateDataValue((prevState) => ({ ...prevState, [name]: updated_value @@ -36,11 +35,6 @@ export const updateValueDirty = const arr: string[] = dirtyFlags; - // TODO remove comments - // console.log('updating ' + name + ' to ' + updated_value); - // console.log('dirtyFlags:', dirtyFlags); - // console.log('binding.ts origData:', origData); - if (origData[name] !== updated_value) { if (!arr.includes(name)) { arr.push(name); diff --git a/mock-api/server.js b/mock-api/server.js index 3b42ce97a..c07cb523a 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -1780,22 +1780,21 @@ const emsesp_devicedata_99 = { }; // CUSTOM ENTITIES -let emsesp_entities = { +let emsesp_entities = [ // entities: [] - entities: [ - { - id: 0, - device_id: 8, - type_id: 24, - offset: 0, - factor: 1, - name: 'boiler_flowtemp', - uom: 1, - value_type: 1, - writeable: true - } - ] -}; + // entities: [ + { + id: 0, + device_id: 8, + type_id: 24, + offset: 0, + factor: 1, + name: 'boiler_flowtemp', + uom: 1, + value_type: 1, + writeable: true + } +]; // SCHEDULE let emsesp_schedule = @@ -2333,7 +2332,7 @@ rest_server.post(EMSESP_WRITE_SCHEDULE_ENDPOINT, (req, res) => { rest_server.post(EMSESP_WRITE_ENTITIES_ENDPOINT, (req, res) => { console.log('write entities'); - console.log(req.body.entities); + console.log(req.body); emsesp_entities = req.body; res.sendStatus(200); }); From 2ae45ecd6e5b416c8c9b62aa8b9a252a561814a0 Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 16 Jun 2023 06:44:49 +0200 Subject: [PATCH 11/30] merging to sync - still need to fix system.ts --- interface/src/api/ap.ts | 15 ++------ interface/src/api/endpoints.ts | 8 ++++ interface/src/api/mqtt.ts | 18 ++------- interface/src/api/network.ts | 30 ++++----------- interface/src/api/ntp.ts | 25 ++++--------- interface/src/api/system.ts | 15 ++++++-- interface/src/framework/ap/APSettingsForm.tsx | 24 ++++++++---- .../src/framework/mqtt/MqttSettingsForm.tsx | 24 ++++++++---- .../src/framework/mqtt/MqttStatusForm.tsx | 7 ++-- .../framework/network/NetworkSettingsForm.tsx | 32 +++++++++------- .../framework/network/NetworkStatusForm.tsx | 7 ++-- .../src/framework/ntp/NTPSettingsForm.tsx | 32 +++++++++++----- interface/src/framework/ntp/NTPStatusForm.tsx | 37 +++++++++++-------- .../src/framework/system/SystemStatusForm.tsx | 6 +-- interface/src/project/DashboardDevices.tsx | 5 +-- interface/src/project/DashboardSensors.tsx | 4 +- interface/src/project/DashboardStatus.tsx | 2 +- interface/src/project/SettingsApplication.tsx | 2 +- .../src/project/SettingsCustomization.tsx | 3 +- mock-api/server.js | 25 +++++++++---- 20 files changed, 171 insertions(+), 150 deletions(-) diff --git a/interface/src/api/ap.ts b/interface/src/api/ap.ts index a314227c2..7ffda7668 100644 --- a/interface/src/api/ap.ts +++ b/interface/src/api/ap.ts @@ -1,14 +1,7 @@ -import { AXIOS, alovaInstance } from './endpoints'; -import type { AxiosPromise } from 'axios'; +import { alovaInstance } from './endpoints'; import type { APSettings, APStatus } from 'types'; -export const readAPStatus = () => alovaInstance.Get('/apStatus'); - -// TODO change APSettings AXIOS to Alova -export function readAPSettings(): AxiosPromise { - return AXIOS.get('/apSettings'); -} -export function updateAPSettings(apSettings: APSettings): AxiosPromise { - return AXIOS.post('/apSettings', apSettings); -} +export const readAPStatus = () => alovaInstance.Get('/rest/apStatus'); +export const readAPSettings = () => alovaInstance.Get('/rest/apSettings'); +export const updateAPSettings = (data: APSettings) => alovaInstance.Post('/rest/apSettings', data); diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index cd1b4b574..cf2fbe799 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -18,6 +18,14 @@ export const EVENT_SOURCE_ROOT = 'http://' + host + '/es/'; export const alovaInstance = createAlova({ statesHook: ReactHook, timeout: 3000, + localCache: { + GET: { + mode: 'placeholder', + // expire: 60 * 10 * 1000 + // see https://alova.js.org/learning/response-cache/#cache-replaceholder-mode + expire: 2000 + } + }, requestAdapter: xhrRequestAdapter(), beforeRequest(method) { if (localStorage.getItem(ACCESS_TOKEN)) { diff --git a/interface/src/api/mqtt.ts b/interface/src/api/mqtt.ts index a2668193b..d75dc4134 100644 --- a/interface/src/api/mqtt.ts +++ b/interface/src/api/mqtt.ts @@ -1,16 +1,6 @@ -import { AXIOS } from './endpoints'; -import type { AxiosPromise } from 'axios'; +import { alovaInstance } from './endpoints'; import type { MqttSettings, MqttStatus } from 'types'; -// TODO move to alova -export function readMqttStatus(): AxiosPromise { - return AXIOS.get('/mqttStatus'); -} - -export function readMqttSettings(): AxiosPromise { - return AXIOS.get('/mqttSettings'); -} - -export function updateMqttSettings(mqttSettings: MqttSettings): AxiosPromise { - return AXIOS.post('/mqttSettings', mqttSettings); -} +export const readMqttStatus = () => alovaInstance.Get('/rest/mqttStatus'); +export const readMqttSettings = () => alovaInstance.Get('/rest/mqttSettings'); +export const updateMqttSettings = (data: MqttSettings) => alovaInstance.Post('/rest/mqttSettings', data); diff --git a/interface/src/api/network.ts b/interface/src/api/network.ts index 2c50bfdf2..03bb7ead6 100644 --- a/interface/src/api/network.ts +++ b/interface/src/api/network.ts @@ -1,25 +1,11 @@ -import { AXIOS } from './endpoints'; -import type { AxiosPromise } from 'axios'; +import { alovaInstance } from './endpoints'; import type { WiFiNetworkList, NetworkSettings, NetworkStatus } from 'types'; -// TODO move to alova -export function readNetworkStatus(): AxiosPromise { - return AXIOS.get('/networkStatus'); -} - -export function scanNetworks(): AxiosPromise { - return AXIOS.get('/scanNetworks'); -} - -export function listNetworks(): AxiosPromise { - return AXIOS.get('/listNetworks'); -} - -export function readNetworkSettings(): AxiosPromise { - return AXIOS.get('/networkSettings'); -} - -export function updateNetworkSettings(wifiSettings: NetworkSettings): AxiosPromise { - return AXIOS.post('/networkSettings', wifiSettings); -} +export const readNetworkStatus = () => alovaInstance.Get('/rest/networkStatus'); +export const scanNetworks = () => alovaInstance.Get('/rest/scanNetworks'); +export const listNetworks = () => alovaInstance.Get('/rest/listNetworks'); +export const readNetworkSettings = () => + alovaInstance.Get('/rest/networkSettings', { name: 'networkSettings' }); +export const updateNetworkSettings = (wifiSettings: NetworkSettings) => + alovaInstance.Post('/rest/networkSettings', wifiSettings); diff --git a/interface/src/api/ntp.ts b/interface/src/api/ntp.ts index c0cd94283..74da189d1 100644 --- a/interface/src/api/ntp.ts +++ b/interface/src/api/ntp.ts @@ -1,20 +1,11 @@ -import { AXIOS } from './endpoints'; -import type { AxiosPromise } from 'axios'; +import { alovaInstance } from './endpoints'; import type { NTPSettings, NTPStatus, Time } from 'types'; -// TODO move to Alova -export function readNTPStatus(): AxiosPromise { - return AXIOS.get('/ntpStatus'); -} +export const readNTPStatus = () => alovaInstance.Get('/rest/ntpStatus'); +export const readNTPSettings = () => + alovaInstance.Get('/rest/ntpSettings', { + name: 'ntpSettings' + }); +export const updateNTPSettings = (data: NTPSettings) => alovaInstance.Post('/rest/ntpSettings', data); -export function readNTPSettings(): AxiosPromise { - return AXIOS.get('/ntpSettings'); -} - -export function updateNTPSettings(ntpSettings: NTPSettings): AxiosPromise { - return AXIOS.post('/ntpSettings', ntpSettings); -} - -export function updateTime(time: Time): AxiosPromise