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); });