more alova ports

This commit is contained in:
Proddy
2023-06-17 13:06:31 +02:00
parent 2ae45ecd6e
commit ed55a96b80
31 changed files with 868 additions and 931 deletions

View File

@@ -50,7 +50,7 @@
"@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/eslint-plugin": "^5.59.11",
"@typescript-eslint/parser": "^5.59.11", "@typescript-eslint/parser": "^5.59.11",
"@vitejs/plugin-react-swc": "^3.3.2", "@vitejs/plugin-react-swc": "^3.3.2",
"eslint": "^8.42.0", "eslint": "^8.43.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",

View File

@@ -33,7 +33,7 @@ const AuthenticatedRouting: FC = () => {
); );
useEffect(() => { useEffect(() => {
// TODO how to replace AXIOS.interceptors.response.use ??? // TODO replace AXIOS.interceptors.response.use ???
const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError); const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError);
return () => AXIOS.interceptors.response.eject(axiosHandlerId); return () => AXIOS.interceptors.response.eject(axiosHandlerId);
}, [handleApiResponseError]); }, [handleApiResponseError]);

View File

@@ -42,6 +42,7 @@ const SignIn: FC = () => {
const signIn = async () => { const signIn = async () => {
try { try {
// TODO move to Alova
const { data: loginResponse } = await AuthenticationApi.signIn(signInRequest); const { data: loginResponse } = await AuthenticationApi.signIn(signInRequest);
authenticationContext.signIn(loginResponse.access_token); authenticationContext.signIn(loginResponse.access_token);
} catch (error) { } catch (error) {

View File

@@ -9,10 +9,11 @@ import type { Me, SignInRequest, SignInResponse } from 'types';
export const SIGN_IN_PATHNAME = 'loginPathname'; export const SIGN_IN_PATHNAME = 'loginPathname';
export const SIGN_IN_SEARCH = 'loginSearch'; export const SIGN_IN_SEARCH = 'loginSearch';
// TODO move to Alova // TODO move verifyAuthorization to Alova
export function verifyAuthorization(): AxiosPromise<void> { export function verifyAuthorization(): AxiosPromise<void> {
return AXIOS.get('/verifyAuthorization'); return AXIOS.get('/verifyAuthorization');
} }
// TODO move signIn to Alova
export function signIn(request: SignInRequest): AxiosPromise<SignInResponse> { export function signIn(request: SignInRequest): AxiosPromise<SignInResponse> {
return AXIOS.post('/signIn', request); return AXIOS.post('/signIn', request);
} }

View File

@@ -17,7 +17,7 @@ export const EVENT_SOURCE_ROOT = 'http://' + host + '/es/';
export const alovaInstance = createAlova({ export const alovaInstance = createAlova({
statesHook: ReactHook, statesHook: ReactHook,
timeout: 3000, // timeout: 3000,
localCache: { localCache: {
GET: { GET: {
mode: 'placeholder', mode: 'placeholder',
@@ -35,7 +35,9 @@ export const alovaInstance = createAlova({
responded: { responded: {
onSuccess: async (response) => { onSuccess: async (response) => {
if (response.status == 205) { if (response.status === 202) {
throw new Error('Wait');
} else if (response.status === 205) {
throw new Error('Reboot required'); throw new Error('Reboot required');
} else if (response.status === 400) { } else if (response.status === 400) {
throw new Error('Request Failed'); throw new Error('Request Failed');
@@ -117,7 +119,7 @@ export const AXIOS_BIN = axios.create({
transformResponse: [(data) => unpack(data)] transformResponse: [(data) => unpack(data)]
}); });
// TODO replace upload with alova, see https://alova.js.org/next-step/download-upload-progress // TODO replace fileupload with alova, see https://alova.js.org/next-step/download-upload-progress
export interface FileUploadConfig { export interface FileUploadConfig {
cancelToken?: CancelToken; cancelToken?: CancelToken;
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void; onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;

View File

@@ -1,9 +1,5 @@
import { AXIOS } from './endpoints'; import { alovaInstance } from './endpoints';
import type { AxiosPromise } from 'axios';
import type { Features } from 'types'; import type { Features } from 'types';
// TODO move to Alova export const readFeatures = () => alovaInstance.Get<Features>('/rest/features');
export function readFeatures(): AxiosPromise<Features> {
return AXIOS.get('/features');
}

View File

@@ -1,52 +1,27 @@
import { AXIOS, AXIOS_BIN, alovaInstance, startUploadFile } from './endpoints'; import { alovaInstance, startUploadFile } from './endpoints';
import type { FileUploadConfig } from './endpoints'; import type { FileUploadConfig } from './endpoints';
import type { AxiosPromise } from 'axios'; import type { AxiosPromise } from 'axios';
import type { OTASettings, SystemStatus, LogSettings, LogEntries } from 'types'; import type { OTASettings, SystemStatus, LogSettings } from 'types';
// TODO move to Alova
// TODO fix this next!
export const readSystemStatus = (timeout?: number) => export const readSystemStatus = (timeout?: number) =>
alovaInstance.Get<SystemStatus>('/rest/systemStatus', { alovaInstance.Get<SystemStatus>('/rest/systemStatus', {
params: { timeout } params: { timeout }
}); });
// export function readSystemStatus(timeout?: number): AxiosPromise<SystemStatus> { // commands
// return AXIOS.get('/systemStatus', { timeout }); export const restart = () => alovaInstance.Post('/rest/restart');
// } export const partition = () => alovaInstance.Post('/rest/partition');
export const factoryReset = () => alovaInstance.Post('/rest/factoryReset');
export function restart(): AxiosPromise<void> { // OTA
return AXIOS.post('/restart'); export const readOTASettings = () => alovaInstance.Get<OTASettings>(`/rest/otaSettings`);
} export const updateOTASettings = (data: any) => alovaInstance.Post('/rest/otaSettings', data);
export function partition(): AxiosPromise<void> { // SystemLog
return AXIOS.post('/partition'); export const readLogSettings = () => alovaInstance.Get<LogSettings>(`/rest/logSettings`);
} export const updateLogSettings = (data: any) => alovaInstance.Post('/rest/logSettings', data);
export const fetchLog = () => alovaInstance.Post('/rest/fetchLog');
export function factoryReset(): AxiosPromise<void> {
return AXIOS.post('/factoryReset');
}
export function readOTASettings(): AxiosPromise<OTASettings> {
return AXIOS.get('/otaSettings');
}
export function updateOTASettings(otaSettings: OTASettings): AxiosPromise<OTASettings> {
return AXIOS.post('/otaSettings', otaSettings);
}
export const uploadFile = (file: File, config?: FileUploadConfig): AxiosPromise<void> => export const uploadFile = (file: File, config?: FileUploadConfig): AxiosPromise<void> =>
startUploadFile('/uploadFile', file, config); startUploadFile('/uploadFile', file, config);
export function readLogSettings(): AxiosPromise<LogSettings> {
return AXIOS.get('/logSettings');
}
export function updateLogSettings(logSettings: LogSettings): AxiosPromise<LogSettings> {
return AXIOS.post('/logSettings', logSettings);
}
export function readLogEntries(): AxiosPromise<LogEntries> {
return AXIOS_BIN.get('/fetchLog');
}

View File

@@ -9,6 +9,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import { extractErrorMessage } from 'utils'; import { extractErrorMessage } from 'utils';
interface MediaUploadOptions { interface MediaUploadOptions {
// TODO fileupload move to alova
upload: (file: File, config?: FileUploadConfig) => AxiosPromise<void>; upload: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
} }

View File

@@ -1,30 +1,14 @@
import { useCallback, useEffect, useState } from 'react'; import { useRequest } from 'alova';
import { FeaturesContext } from '.'; import { FeaturesContext } from '.';
import type { FC } from 'react'; import type { FC } from 'react';
import type { Features } from 'types';
import type { RequiredChildrenProps } from 'utils'; import type { RequiredChildrenProps } from 'utils';
import * as FeaturesApi from 'api/features'; import * as FeaturesApi from 'api/features';
import { ApplicationError, LoadingSpinner } from 'components'; import { ApplicationError, LoadingSpinner } from 'components';
import { extractErrorMessage } from 'utils';
const FeaturesLoader: FC<RequiredChildrenProps> = (props) => { const FeaturesLoader: FC<RequiredChildrenProps> = (props) => {
const [errorMessage, setErrorMessage] = useState<string>(); const { data: features, error } = useRequest(FeaturesApi.readFeatures);
const [features, setFeatures] = useState<Features>();
const loadFeatures = useCallback(async () => {
try {
const response = await FeaturesApi.readFeatures();
setFeatures(response.data);
} catch (error) {
setErrorMessage(extractErrorMessage(error, 'Failed to fetch application details.'));
}
}, []);
useEffect(() => {
void loadFeatures();
}, [loadFeatures]);
if (features) { if (features) {
return ( return (
@@ -38,8 +22,8 @@ const FeaturesLoader: FC<RequiredChildrenProps> = (props) => {
); );
} }
if (errorMessage) { if (error) {
return <ApplicationError message={errorMessage} />; return <ApplicationError message={error?.message} />;
} }
return <LoadingSpinner height="100vh" />; return <LoadingSpinner height="100vh" />;

View File

@@ -18,6 +18,7 @@ import {
InputAdornment, InputAdornment,
TextField TextField
} from '@mui/material'; } from '@mui/material';
// eslint-disable-next-line import/named
import { updateState, useRequest } from 'alova'; import { updateState, useRequest } from 'alova';
import { useContext, useEffect, useState } from 'react'; import { useContext, useEffect, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
@@ -29,6 +30,7 @@ import type { FC } from 'react';
import type { NetworkSettings } from 'types'; import type { NetworkSettings } from 'types';
import * as NetworkApi from 'api/network'; import * as NetworkApi from 'api/network';
import * as SystemApi from 'api/system';
import { import {
BlockFormControlLabel, BlockFormControlLabel,
ButtonRow, ButtonRow,
@@ -40,7 +42,7 @@ import {
BlockNavigation BlockNavigation
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from 'project/api';
import { numberValue, updateValueDirty, useRest2 } from 'utils'; import { numberValue, updateValueDirty, useRest2 } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
@@ -71,7 +73,7 @@ const WiFiSettingsForm: FC = () => {
update: NetworkApi.updateNetworkSettings update: NetworkApi.updateNetworkSettings
}); });
const { send: restartCommand } = useRequest(EMSESP.restart(), { const { send: restartCommand } = useRequest(SystemApi.restart(), {
immediate: false immediate: false
}); });

View File

@@ -1,82 +1,63 @@
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi'; import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
import { useEffect, useState, useCallback, useRef } from 'react'; import { useRequest } from 'alova';
import { useState, useCallback, useRef } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import WiFiNetworkSelector from './WiFiNetworkSelector'; import WiFiNetworkSelector from './WiFiNetworkSelector';
import type { FC } from 'react'; import type { FC } from 'react';
import type { WiFiNetwork, WiFiNetworkList } from 'types';
import * as NetworkApi from 'api/network'; import * as NetworkApi from 'api/network';
import { ButtonRow, FormLoader, SectionContent } from 'components'; import { ButtonRow, FormLoader, SectionContent } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
const NUM_POLLS = 10; const NUM_POLLS = 10;
const POLLING_FREQUENCY = 500; const POLLING_FREQUENCY = 1000;
const compareNetworks = (network1: WiFiNetwork, network2: WiFiNetwork) => {
if (network1.rssi < network2.rssi) return 1;
if (network1.rssi > network2.rssi) return -1;
return 0;
};
const WiFiNetworkScanner: FC = () => { const WiFiNetworkScanner: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const pollCount = useRef(0); const pollCount = useRef(0);
const [networkList, setNetworkList] = useState<WiFiNetworkList>();
const [errorMessage, setErrorMessage] = useState<string>(); const [errorMessage, setErrorMessage] = useState<string>();
const { data: networkList, send: getNetworkList } = useRequest(NetworkApi.listNetworks, {
immediate: false
});
const {
send: scanNetworks,
onSuccess: onSuccessScanNetworks,
onError: onErrorScanNetworks
} = useRequest(NetworkApi.scanNetworks);
const finishedWithError = useCallback((message: string) => { const finishedWithError = useCallback((message: string) => {
toast.error(message); toast.error(message);
setNetworkList(undefined);
setErrorMessage(message); setErrorMessage(message);
pollCount.current = 0;
}, []); }, []);
const pollNetworkList = useCallback(async () => { onErrorScanNetworks((event) => {
try { console.log('onErrorScanNetworks'); // TODO fix
const response = await NetworkApi.listNetworks(); if (event.error?.message === 'Wait') {
if (response.status === 202) { // 202
const completedPollCount = pollCount.current + 1; console.log('not ready...: ', event.error?.message); // TODO fix
if (completedPollCount < NUM_POLLS) { const completedPollCount = pollCount.current + 1;
pollCount.current = completedPollCount; if (completedPollCount < NUM_POLLS) {
setTimeout(pollNetworkList, POLLING_FREQUENCY); pollCount.current = completedPollCount;
} else { setTimeout(scanNetworks, POLLING_FREQUENCY);
finishedWithError(LL.PROBLEM_LOADING());
}
} else {
const newNetworkList = response.data;
newNetworkList.networks.sort(compareNetworks);
setNetworkList(newNetworkList);
}
} catch (error) {
if (error.response) {
finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message);
} else { } else {
finishedWithError(LL.PROBLEM_LOADING()); finishedWithError(LL.PROBLEM_LOADING());
} }
} else {
finishedWithError(LL.PROBLEM_LOADING());
} }
}, [finishedWithError, LL]); });
const startNetworkScan = useCallback(async () => { onSuccessScanNetworks(() => {
console.log('onCompleteScanNetworks'); // TODO fix
pollCount.current = 0; pollCount.current = 0;
setNetworkList(undefined); void getNetworkList(); // fetch the list
setErrorMessage(undefined); });
try {
await NetworkApi.scanNetworks();
setTimeout(pollNetworkList, POLLING_FREQUENCY);
} catch (error) {
if (error.response) {
finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message);
} else {
finishedWithError(LL.PROBLEM_LOADING());
}
}
}, [finishedWithError, pollNetworkList, LL]);
useEffect(() => {
void startNetworkScan();
}, [startNetworkScan]);
const renderNetworkScanner = () => { const renderNetworkScanner = () => {
if (!networkList) { if (!networkList) {
@@ -93,7 +74,7 @@ const WiFiNetworkScanner: FC = () => {
startIcon={<PermScanWifiIcon />} startIcon={<PermScanWifiIcon />}
variant="outlined" variant="outlined"
color="secondary" color="secondary"
onClick={startNetworkScan} onClick={scanNetworks}
disabled={!errorMessage && !networkList} disabled={!errorMessage && !networkList}
> >
{LL.SCAN_AGAIN()}&hellip; {LL.SCAN_AGAIN()}&hellip;

View File

@@ -1,6 +1,7 @@
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { Button, Checkbox, MenuItem } from '@mui/material'; import { Button, Checkbox, MenuItem } from '@mui/material';
// eslint-disable-next-line import/named
import { updateState } from 'alova'; import { updateState } from 'alova';
import { useState } from 'react'; import { useState } from 'react';
import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ'; import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ';

View File

@@ -12,6 +12,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from 'project/api'; import * as EMSESP from 'project/api';
interface UploadFileProps { interface UploadFileProps {
// TODO fileupload upload move to alova
uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>; uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
} }

View File

@@ -18,21 +18,31 @@ import {
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValueDirty, useRest } from 'utils'; import { numberValue, updateValueDirty, useRest2 } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
import { OTA_SETTINGS_VALIDATOR } from 'validators/system'; import { OTA_SETTINGS_VALIDATOR } from 'validators/system';
const OTASettingsForm: FC = () => { const OTASettingsForm: FC = () => {
const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } = const {
useRest<OTASettings>({ loadData,
read: SystemApi.readOTASettings, saveData,
update: SystemApi.updateOTASettings saving,
}); updateDataValue,
data,
origData,
dirtyFlags,
setDirtyFlags,
blocker,
errorMessage
} = useRest2<OTASettings>({
read: SystemApi.readOTASettings,
update: SystemApi.updateOTASettings
});
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData); const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();

View File

@@ -1,3 +1,4 @@
import { useRequest } from 'alova';
import { useRef, useState, useEffect } from 'react'; import { useRef, useState, useEffect } from 'react';
import type { FC } from 'react'; import type { FC } from 'react';
@@ -16,10 +17,14 @@ const RestartMonitor: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const { send: readSystemStatus } = useRequest((timeout) => SystemApi.readSystemStatus(timeout), {
immediate: false
});
const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT); const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT);
const poll = useRef(async () => { const poll = useRef(async () => {
try { try {
await SystemApi.readSystemStatus(POLL_TIMEOUT); await readSystemStatus(POLL_TIMEOUT);
document.location.href = '/fileUpdated'; document.location.href = '/fileUpdated';
} catch (error) { } catch (error) {
if (new Date().getTime() < timeoutAt.current) { if (new Date().getTime() < timeoutAt.current) {

View File

@@ -1,11 +1,13 @@
import DownloadIcon from '@mui/icons-material/GetApp'; import DownloadIcon from '@mui/icons-material/GetApp';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { Box, styled, Button, Checkbox, MenuItem, Grid, TextField } from '@mui/material'; import { Box, styled, Button, Checkbox, MenuItem, Grid, TextField } from '@mui/material';
import { useState, useEffect, useCallback } from 'react'; // eslint-disable-next-line import/named
import { useRequest } from 'alova';
import { useState, useEffect } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import type { FC } from 'react'; import type { FC } from 'react';
import type { LogSettings, LogEntry, LogEntries } from 'types'; import type { LogSettings, LogEntry } from 'types';
import { addAccessTokenParameter } from 'api/authentication'; import { addAccessTokenParameter } from 'api/authentication';
import { EVENT_SOURCE_ROOT } from 'api/endpoints'; import { EVENT_SOURCE_ROOT } from 'api/endpoints';
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
@@ -14,7 +16,7 @@ import { SectionContent, FormLoader, BlockFormControlLabel, BlockNavigation } fr
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { LogLevel } from 'types'; import { LogLevel } from 'types';
import { useRest, updateValueDirty, extractErrorMessage } from 'utils'; import { updateValueDirty, useRest2 } from 'utils';
export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log'; export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log';
@@ -49,15 +51,20 @@ const levelLabel = (level: LogLevel) => {
const SystemLog: FC = () => { const SystemLog: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
// TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus); const { loadData, data, updateDataValue, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
const { loadData, data, setData, origData, dirtyFlags, blocker, setDirtyFlags, setOrigData } = useRest<LogSettings>({ useRest2<LogSettings>({
read: SystemApi.readLogSettings read: SystemApi.readLogSettings,
}); update: SystemApi.updateLogSettings
});
const [errorMessage, setErrorMessage] = useState<string>(); // called on page load to reset pointer and fetch all log entries
const [logEntries, setLogEntries] = useState<LogEntries>({ events: [] }); // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { send: fetchLog } = useRequest(SystemApi.fetchLog());
const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
const [lastIndex, setLastIndex] = useState<number>(0); const [lastIndex, setLastIndex] = useState<number>(0);
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue);
const paddedLevelLabel = (level: LogLevel) => { const paddedLevelLabel = (level: LogLevel) => {
const label = levelLabel(level); const label = levelLabel(level);
return data?.compact ? ' ' + label[0] : label.padStart(8, '\xa0'); return data?.compact ? ' ' + label[0] : label.padStart(8, '\xa0');
@@ -73,11 +80,9 @@ const SystemLog: FC = () => {
return data?.compact ? label : label.padEnd(7, '\xa0'); return data?.compact ? label : label.padEnd(7, '\xa0');
}; };
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
const onDownload = () => { const onDownload = () => {
let result = ''; let result = '';
for (const i of logEntries.events) { for (const i of logEntries) {
result += i.t + ' ' + levelLabel(i.l) + ' ' + i.i + ': [' + i.n + '] ' + i.m + '\n'; result += i.t + ' ' + levelLabel(i.l) + ' ' + i.i + ': [' + i.n + '] ' + i.m + '\n';
} }
const a = document.createElement('a'); const a = document.createElement('a');
@@ -94,29 +99,22 @@ const SystemLog: FC = () => {
const logentry = JSON.parse(rawData as string) as LogEntry; const logentry = JSON.parse(rawData as string) as LogEntry;
if (logentry.i > lastIndex) { if (logentry.i > lastIndex) {
setLastIndex(logentry.i); setLastIndex(logentry.i);
setLogEntries((old) => ({ events: [...old.events, logentry] })); setLogEntries((log) => [...log, logentry]);
} }
} }
}; };
const fetchLog = useCallback(async () => { const saveSettings = async () => {
try { await saveData();
await SystemApi.readLogEntries(); };
} catch (error) {
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
}, [LL]);
useEffect(() => {
void fetchLog();
}, [fetchLog]);
useEffect(() => { useEffect(() => {
const es = new EventSource(addAccessTokenParameter(LOG_EVENTSOURCE_URL)); const es = new EventSource(addAccessTokenParameter(LOG_EVENTSOURCE_URL));
es.onmessage = onMessage; es.onmessage = onMessage;
es.onerror = () => { es.onerror = () => {
es.close(); es.close();
window.location.reload(); toast.error('EventSource failed');
// window.location.reload();
}; };
return () => { return () => {
@@ -124,28 +122,6 @@ const SystemLog: FC = () => {
}; };
}); });
const saveSettings = async () => {
if (data) {
try {
const response = await SystemApi.updateLogSettings({
level: data.level,
max_messages: data.max_messages,
compact: data.compact
});
if (response.status !== 200) {
toast.error(LL.PROBLEM_UPDATING());
} else {
setOrigData(response.data);
setDirtyFlags([]);
toast.success(LL.UPDATED_OF(LL.SETTINGS()));
}
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
}
}
};
const content = () => { const content = () => {
if (!data) { if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />; return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
@@ -231,17 +207,16 @@ const SystemLog: FC = () => {
p: 1 p: 1
}} }}
> >
{logEntries && {logEntries.map((e) => (
logEntries.events.map((e) => ( <LogEntryLine key={e.i}>
<LogEntryLine key={e.i}> <span>{e.t}</span>
<span>{e.t}</span> {data.compact && <span>{paddedLevelLabel(e.l)} </span>}
{data.compact && <span>{paddedLevelLabel(e.l)} </span>} {!data.compact && <span>{paddedLevelLabel(e.l)}&nbsp;</span>}
{!data.compact && <span>{paddedLevelLabel(e.l)}&nbsp;</span>} <span>{paddedIDLabel(e.i)} </span>
<span>{paddedIDLabel(e.i)} </span> <span>{paddedNameLabel(e.n)} </span>
<span>{paddedNameLabel(e.n)} </span> <span>{e.m}</span>
<span>{e.m}</span> </LogEntryLine>
</LogEntryLine> ))}
))}
</Box> </Box>
</> </>
); );

View File

@@ -35,12 +35,11 @@ import { toast } from 'react-toastify';
import RestartMonitor from './RestartMonitor'; import RestartMonitor from './RestartMonitor';
import type { FC } from 'react'; import type { FC } from 'react';
import type { SystemStatus, Version } from 'types'; import type { Version } from 'types';
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { ButtonRow, FormLoader, SectionContent, MessageBox } from 'components'; import { ButtonRow, FormLoader, SectionContent, MessageBox } from 'components';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { extractErrorMessage } from 'utils';
export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest'; export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest';
export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest'; export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest';
@@ -52,9 +51,6 @@ function formatNumber(num: number) {
const SystemStatusForm: FC = () => { const SystemStatusForm: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [restarting, setRestarting] = useState<boolean>();
const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus);
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
const [confirmRestart, setConfirmRestart] = useState<boolean>(false); const [confirmRestart, setConfirmRestart] = useState<boolean>(false);
@@ -63,6 +59,21 @@ const SystemStatusForm: FC = () => {
const [showingVersion, setShowingVersion] = useState<boolean>(false); const [showingVersion, setShowingVersion] = useState<boolean>(false);
const [latestVersion, setLatestVersion] = useState<Version>(); const [latestVersion, setLatestVersion] = useState<Version>();
const [latestDevVersion, setLatestDevVersion] = useState<Version>(); const [latestDevVersion, setLatestDevVersion] = useState<Version>();
const [restarting, setRestarting] = useState<boolean>();
const { send: restartCommand } = useRequest(SystemApi.restart(), {
immediate: false
});
const { send: factoryResetCommand } = useRequest(SystemApi.factoryReset(), {
immediate: false
});
const { send: partitionCommand } = useRequest(SystemApi.partition(), {
immediate: false
});
const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus);
useEffect(() => { useEffect(() => {
void axios.get(VERSIONCHECK_ENDPOINT).then((response) => { void axios.get(VERSIONCHECK_ENDPOINT).then((response) => {
@@ -83,30 +94,47 @@ const SystemStatusForm: FC = () => {
const restart = async () => { const restart = async () => {
setProcessing(true); setProcessing(true);
try { await restartCommand()
const response = await SystemApi.restart(); .then(() => {
if (response.status === 200) {
setRestarting(true); setRestarting(true);
} })
} catch (error) { .catch((err) => {
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); toast.error(err.message);
} finally { })
setConfirmRestart(false); .finally(() => {
setProcessing(false); setConfirmRestart(false);
} setProcessing(false);
});
};
const factoryReset = async () => {
setProcessing(true);
await factoryResetCommand()
.then(() => {
setRestarting(true);
})
.catch((err) => {
toast.error(err.message);
})
.finally(() => {
setConfirmFactoryReset(false);
setProcessing(false);
});
}; };
const partition = async () => { const partition = async () => {
setProcessing(true); setProcessing(true);
try { await partitionCommand()
await SystemApi.partition(); .then(() => {
setRestarting(true); setRestarting(true);
} catch (error) { })
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); .catch((err) => {
} finally { toast.error(err.message);
setConfirmRestart(false); })
setProcessing(false); .finally(() => {
} setConfirmRestart(false);
setProcessing(false);
});
}; };
const renderRestartDialog = () => ( const renderRestartDialog = () => (
@@ -201,19 +229,6 @@ const SystemStatusForm: FC = () => {
</Dialog> </Dialog>
); );
const factoryReset = async () => {
setProcessing(true);
try {
await SystemApi.factoryReset();
setRestarting(true);
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
} finally {
setConfirmFactoryReset(false);
setProcessing(false);
}
};
const renderFactoryResetDialog = () => ( const renderFactoryResetDialog = () => (
<Dialog open={confirmFactoryReset} onClose={() => setConfirmFactoryReset(false)}> <Dialog open={confirmFactoryReset} onClose={() => setConfirmFactoryReset(false)}>
<DialogTitle>{LL.FACTORY_RESET()}</DialogTitle> <DialogTitle>{LL.FACTORY_RESET()}</DialogTitle>
@@ -243,7 +258,7 @@ const SystemStatusForm: FC = () => {
const content = () => { const content = () => {
if (!data) { if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />; return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
} }
return ( return (

View File

@@ -15,6 +15,7 @@ const UploadFileForm: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => { const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => {
// TODO fileupload move to alova
const response = await SystemApi.uploadFile(file, config); const response = await SystemApi.uploadFile(file, config);
if (response.status === 200) { if (response.status === 200) {
setRestarting(true); setRestarting(true);

View File

@@ -12,6 +12,7 @@ import { createSettingsValidator } from './validators';
import type { Settings } from './types'; import type { Settings } from './types';
import type { ValidateFieldsError } from 'async-validator'; import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react'; import type { FC } from 'react';
import * as SystemApi from 'api/system';
import { import {
SectionContent, SectionContent,
FormLoader, FormLoader,
@@ -69,7 +70,7 @@ const SettingsApplication: FC = () => {
immediate: false immediate: false
}); });
const { send: restartCommand } = useRequest(EMSESP.restart(), { const { send: restartCommand } = useRequest(SystemApi.restart(), {
immediate: false immediate: false
}); });

View File

@@ -35,6 +35,7 @@ import * as EMSESP from './api';
import { DeviceEntityMask } from './types'; import { DeviceEntityMask } from './types';
import type { DeviceShort, DeviceEntity } from './types'; import type { DeviceShort, DeviceEntity } from './types';
import type { FC } from 'react'; import type { FC } from 'react';
import * as SystemApi from 'api/system';
import { ButtonRow, SectionContent, MessageBox, BlockNavigation } from 'components'; import { ButtonRow, SectionContent, MessageBox, BlockNavigation } from 'components';
import RestartMonitor from 'framework/system/RestartMonitor'; import RestartMonitor from 'framework/system/RestartMonitor';
@@ -78,7 +79,7 @@ const SettingsCustomization: FC = () => {
setOriginalSettings(event.data); setOriginalSettings(event.data);
}); });
const { send: restartCommand } = useRequest(EMSESP.restart(), { const { send: restartCommand } = useRequest(SystemApi.restart(), {
immediate: false immediate: false
}); });

View File

@@ -4,6 +4,7 @@ import WarningIcon from '@mui/icons-material/Warning';
import { Button, Typography, Box } from '@mui/material'; import { Button, Typography, Box } from '@mui/material';
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; 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 { useTheme } from '@table-library/react-table-library/theme';
// eslint-disable-next-line import/named
import { updateState, useRequest } from 'alova'; import { updateState, useRequest } from 'alova';
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { unstable_useBlocker as useBlocker } from 'react-router-dom'; import { unstable_useBlocker as useBlocker } from 'react-router-dom';
@@ -108,8 +109,8 @@ const SettingsEntities: FC = () => {
}); });
const saveEntities = async () => { const saveEntities = async () => {
await writeEntities( await writeEntities({
entities entities: entities
.filter((ei) => !ei.deleted) .filter((ei) => !ei.deleted)
.map((condensed_ei) => ({ .map((condensed_ei) => ({
id: condensed_ei.id, id: condensed_ei.id,
@@ -122,7 +123,7 @@ const SettingsEntities: FC = () => {
writeable: condensed_ei.writeable, writeable: condensed_ei.writeable,
value_type: condensed_ei.value_type value_type: condensed_ei.value_type
})) }))
) })
.then(() => { .then(() => {
toast.success(LL.ENTITIES_UPDATED()); toast.success(LL.ENTITIES_UPDATED());
}) })

View File

@@ -6,6 +6,7 @@ import WarningIcon from '@mui/icons-material/Warning';
import { Box, Typography, Divider, Stack, Button } from '@mui/material'; 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 { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme'; import { useTheme } from '@table-library/react-table-library/theme';
// eslint-disable-next-line import/named
import { updateState, useRequest } from 'alova'; import { updateState, useRequest } from 'alova';
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { unstable_useBlocker as useBlocker } from 'react-router-dom'; import { unstable_useBlocker as useBlocker } from 'react-router-dom';
@@ -108,8 +109,8 @@ const SettingsScheduler: FC = () => {
}); });
const saveSchedule = async () => { const saveSchedule = async () => {
await writeSchedule( await writeSchedule({
schedule schedule: schedule
.filter((si) => !si.deleted) .filter((si) => !si.deleted)
.map((condensed_si) => ({ .map((condensed_si) => ({
id: condensed_si.id, id: condensed_si.id,
@@ -120,7 +121,7 @@ const SettingsScheduler: FC = () => {
value: condensed_si.value, value: condensed_si.value,
name: condensed_si.name name: condensed_si.name
})) }))
) })
.then(() => { .then(() => {
toast.success(LL.SCHEDULE_UPDATED()); toast.success(LL.SCHEDULE_UPDATED());
}) })

View File

@@ -31,7 +31,6 @@ export const getBoardProfile = (boardProfile: string) =>
alovaInstance.Get('/rest/boardProfile', { alovaInstance.Get('/rest/boardProfile', {
params: { boardProfile } params: { boardProfile }
}); });
export const restart = () => alovaInstance.Post('/rest/restart');
// DashboardSensors // DashboardSensors
export const readSensorData = () => alovaInstance.Get<SensorData>('/rest/sensorData'); export const readSensorData = () => alovaInstance.Get<SensorData>('/rest/sensorData');
@@ -70,7 +69,7 @@ export const readSchedule = () =>
alovaInstance.Get<ScheduleItem[]>('/rest/schedule', { alovaInstance.Get<ScheduleItem[]>('/rest/schedule', {
name: 'schedule', name: 'schedule',
transformData(data: any) { transformData(data: any) {
return data.map((si: ScheduleItem) => ({ return data.schedule.map((si: ScheduleItem) => ({
...si, ...si,
o_id: si.id, o_id: si.id,
o_active: si.active, o_active: si.active,
@@ -85,12 +84,12 @@ export const readSchedule = () =>
}); });
export const writeSchedule = (data: any) => alovaInstance.Post('/rest/schedule', data); export const writeSchedule = (data: any) => alovaInstance.Post('/rest/schedule', data);
// SettingsCustomization // SettingsEntities
export const readEntities = () => export const readEntities = () =>
alovaInstance.Get<EntityItem[]>('/rest/entities', { alovaInstance.Get<EntityItem[]>('/rest/entities', {
name: 'entities', name: 'entities',
transformData(data: any) { transformData(data: any) {
return data.map((ei: EntityItem) => ({ return data.entities.map((ei: EntityItem) => ({
...ei, ...ei,
o_id: ei.id, o_id: ei.id,
o_device_id: ei.device_id, o_device_id: ei.device_id,

View File

@@ -42,10 +42,6 @@ export interface LogEntry {
m: string; m: string;
} }
export interface LogEntries {
events: LogEntry[];
}
export interface LogSettings { export interface LogSettings {
level: number; level: number;
max_messages: number; max_messages: number;

View File

@@ -620,10 +620,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@eslint/js@npm:8.42.0": "@eslint/js@npm:8.43.0":
version: 8.42.0 version: 8.43.0
resolution: "@eslint/js@npm:8.42.0" resolution: "@eslint/js@npm:8.43.0"
checksum: 4ae46df1f32095cf9527d1f6a8a30512151f8eb66dd883a226face17c9e7cfdd2dcb3d4e3124fb67ac5801e0a776b1d7bba368276cfb1e1e4eefb047e38b41d6 checksum: ff1a1587e8f28c21dda36a331cf70ca16b76e5897cecf10f6b4c326abddf18db565ee5f71feb89cbb0d3d20ff321a2536357562c0233868eed70784640b73cf4
languageName: node languageName: node
linkType: hard linkType: hard
@@ -1546,7 +1546,7 @@ __metadata:
alova: ^2.6.1 alova: ^2.6.1
async-validator: ^4.2.5 async-validator: ^4.2.5
axios: ^1.4.0 axios: ^1.4.0
eslint: ^8.42.0 eslint: ^8.43.0
eslint-config-airbnb: ^19.0.4 eslint-config-airbnb: ^19.0.4
eslint-config-airbnb-typescript: ^17.0.0 eslint-config-airbnb-typescript: ^17.0.0
eslint-config-prettier: ^8.8.0 eslint-config-prettier: ^8.8.0
@@ -2850,14 +2850,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"eslint@npm:^8.42.0": "eslint@npm:^8.43.0":
version: 8.42.0 version: 8.43.0
resolution: "eslint@npm:8.42.0" resolution: "eslint@npm:8.43.0"
dependencies: dependencies:
"@eslint-community/eslint-utils": ^4.2.0 "@eslint-community/eslint-utils": ^4.2.0
"@eslint-community/regexpp": ^4.4.0 "@eslint-community/regexpp": ^4.4.0
"@eslint/eslintrc": ^2.0.3 "@eslint/eslintrc": ^2.0.3
"@eslint/js": 8.42.0 "@eslint/js": 8.43.0
"@humanwhocodes/config-array": ^0.11.10 "@humanwhocodes/config-array": ^0.11.10
"@humanwhocodes/module-importer": ^1.0.1 "@humanwhocodes/module-importer": ^1.0.1
"@nodelib/fs.walk": ^1.2.8 "@nodelib/fs.walk": ^1.2.8
@@ -2895,7 +2895,7 @@ __metadata:
text-table: ^0.2.0 text-table: ^0.2.0
bin: bin:
eslint: bin/eslint.js eslint: bin/eslint.js
checksum: 8ab5a3c1619008c946497a16b88a811b1f6c49a750a9bd0f81085dff4166418b9da4e46108b09d920877ab2f5981e3613332653b7f5e3917d8088bc4b8d40b5a checksum: 1f9ff2c774e852c179ba569a3b672cbc4cf91aa59843ee32f7da363c10b5aad842672005ac04c760f6077b3471da428562274e0fcb0a78c2056866b3d36be948
languageName: node languageName: node
linkType: hard linkType: hard

View File

@@ -20,188 +20,198 @@ template <typename T, typename Enable = void>
struct Comparer; struct Comparer;
template <typename T> template <typename T>
struct Comparer<T, typename enable_if<IsString<T>::value>::type> struct Comparer<T, typename enable_if<IsString<T>::value>::type> : ComparerBase {
: ComparerBase { T rhs;
T rhs; // TODO: store adapted string?
explicit Comparer(T value) : rhs(value) {} explicit Comparer(T value)
: rhs(value) {
}
CompareResult visitString(const char* lhs, size_t n) { CompareResult visitString(const char * lhs, size_t n) {
int i = stringCompare(adaptString(rhs), adaptString(lhs, n)); int i = stringCompare(adaptString(rhs), adaptString(lhs, n));
if (i < 0) if (i < 0)
return COMPARE_RESULT_GREATER; return COMPARE_RESULT_GREATER;
else if (i > 0) else if (i > 0)
return COMPARE_RESULT_LESS; return COMPARE_RESULT_LESS;
else else
return COMPARE_RESULT_EQUAL; return COMPARE_RESULT_EQUAL;
} }
CompareResult visitNull() { CompareResult visitNull() {
if (adaptString(rhs).isNull()) if (adaptString(rhs).isNull())
return COMPARE_RESULT_EQUAL; return COMPARE_RESULT_EQUAL;
else else
return COMPARE_RESULT_DIFFER; return COMPARE_RESULT_DIFFER;
} }
}; };
template <typename T> template <typename T>
struct Comparer<T, typename enable_if<is_integral<T>::value || struct Comparer<T, typename enable_if<is_integral<T>::value || is_floating_point<T>::value>::type> : ComparerBase {
is_floating_point<T>::value>::type> T rhs;
: ComparerBase {
T rhs;
explicit Comparer(T value) : rhs(value) {} explicit Comparer(T value)
: rhs(value) {
}
CompareResult visitFloat(JsonFloat lhs) { CompareResult visitFloat(JsonFloat lhs) {
return arithmeticCompare(lhs, rhs); return arithmeticCompare(lhs, rhs);
} }
CompareResult visitSignedInteger(JsonInteger lhs) { CompareResult visitSignedInteger(JsonInteger lhs) {
return arithmeticCompare(lhs, rhs); return arithmeticCompare(lhs, rhs);
} }
CompareResult visitUnsignedInteger(JsonUInt lhs) { CompareResult visitUnsignedInteger(JsonUInt lhs) {
return arithmeticCompare(lhs, rhs); return arithmeticCompare(lhs, rhs);
} }
CompareResult visitBoolean(bool lhs) { CompareResult visitBoolean(bool lhs) {
return visitUnsignedInteger(static_cast<JsonUInt>(lhs)); return visitUnsignedInteger(static_cast<JsonUInt>(lhs));
} }
}; };
struct NullComparer : ComparerBase { struct NullComparer : ComparerBase {
CompareResult visitNull() { CompareResult visitNull() {
return COMPARE_RESULT_EQUAL; return COMPARE_RESULT_EQUAL;
} }
}; };
template <> template <>
struct Comparer<decltype(nullptr), void> : NullComparer { struct Comparer<decltype(nullptr), void> : NullComparer {
explicit Comparer(decltype(nullptr)) : NullComparer() {} explicit Comparer(decltype(nullptr))
: NullComparer() {
}
}; };
struct ArrayComparer : ComparerBase { struct ArrayComparer : ComparerBase {
const CollectionData* _rhs; const CollectionData * _rhs;
explicit ArrayComparer(const CollectionData& rhs) : _rhs(&rhs) {} explicit ArrayComparer(const CollectionData & rhs)
: _rhs(&rhs) {
}
CompareResult visitArray(const CollectionData& lhs) { CompareResult visitArray(const CollectionData & lhs) {
if (JsonArrayConst(&lhs) == JsonArrayConst(_rhs)) if (JsonArrayConst(&lhs) == JsonArrayConst(_rhs))
return COMPARE_RESULT_EQUAL; return COMPARE_RESULT_EQUAL;
else else
return COMPARE_RESULT_DIFFER; return COMPARE_RESULT_DIFFER;
} }
}; };
struct ObjectComparer : ComparerBase { struct ObjectComparer : ComparerBase {
const CollectionData* _rhs; const CollectionData * _rhs;
explicit ObjectComparer(const CollectionData& rhs) : _rhs(&rhs) {} explicit ObjectComparer(const CollectionData & rhs)
: _rhs(&rhs) {
}
CompareResult visitObject(const CollectionData& lhs) { CompareResult visitObject(const CollectionData & lhs) {
if (JsonObjectConst(&lhs) == JsonObjectConst(_rhs)) if (JsonObjectConst(&lhs) == JsonObjectConst(_rhs))
return COMPARE_RESULT_EQUAL; return COMPARE_RESULT_EQUAL;
else else
return COMPARE_RESULT_DIFFER; return COMPARE_RESULT_DIFFER;
} }
}; };
struct RawComparer : ComparerBase { struct RawComparer : ComparerBase {
const char* _rhsData; const char * _rhsData;
size_t _rhsSize; size_t _rhsSize;
explicit RawComparer(const char* rhsData, size_t rhsSize) explicit RawComparer(const char * rhsData, size_t rhsSize)
: _rhsData(rhsData), _rhsSize(rhsSize) {} : _rhsData(rhsData)
, _rhsSize(rhsSize) {
}
CompareResult visitRawJson(const char* lhsData, size_t lhsSize) { CompareResult visitRawJson(const char * lhsData, size_t lhsSize) {
size_t size = _rhsSize < lhsSize ? _rhsSize : lhsSize; size_t size = _rhsSize < lhsSize ? _rhsSize : lhsSize;
int n = memcmp(lhsData, _rhsData, size); int n = memcmp(lhsData, _rhsData, size);
if (n < 0) if (n < 0)
return COMPARE_RESULT_LESS; return COMPARE_RESULT_LESS;
else if (n > 0) else if (n > 0)
return COMPARE_RESULT_GREATER; return COMPARE_RESULT_GREATER;
else else
return COMPARE_RESULT_EQUAL; return COMPARE_RESULT_EQUAL;
} }
}; };
struct VariantComparer : ComparerBase { struct VariantComparer : ComparerBase {
const VariantData* rhs; const VariantData * rhs;
explicit VariantComparer(const VariantData* value) : rhs(value) {} explicit VariantComparer(const VariantData * value)
: rhs(value) {
CompareResult visitArray(const CollectionData& lhs) { }
ArrayComparer comparer(lhs);
return accept(comparer); CompareResult visitArray(const CollectionData & lhs) {
} ArrayComparer comparer(lhs);
return accept(comparer);
CompareResult visitObject(const CollectionData& lhs) { }
ObjectComparer comparer(lhs);
return accept(comparer); CompareResult visitObject(const CollectionData & lhs) {
} ObjectComparer comparer(lhs);
return accept(comparer);
CompareResult visitFloat(JsonFloat lhs) { }
Comparer<JsonFloat> comparer(lhs);
return accept(comparer); CompareResult visitFloat(JsonFloat lhs) {
} Comparer<JsonFloat> comparer(lhs);
return accept(comparer);
CompareResult visitString(const char* lhs, size_t) { }
Comparer<const char*> comparer(lhs);
return accept(comparer); CompareResult visitString(const char * lhs, size_t) {
} Comparer<const char *> comparer(lhs);
return accept(comparer);
CompareResult visitRawJson(const char* lhsData, size_t lhsSize) { }
RawComparer comparer(lhsData, lhsSize);
return accept(comparer); CompareResult visitRawJson(const char * lhsData, size_t lhsSize) {
} RawComparer comparer(lhsData, lhsSize);
return accept(comparer);
CompareResult visitSignedInteger(JsonInteger lhs) { }
Comparer<JsonInteger> comparer(lhs);
return accept(comparer); CompareResult visitSignedInteger(JsonInteger lhs) {
} Comparer<JsonInteger> comparer(lhs);
return accept(comparer);
CompareResult visitUnsignedInteger(JsonUInt lhs) { }
Comparer<JsonUInt> comparer(lhs);
return accept(comparer); CompareResult visitUnsignedInteger(JsonUInt lhs) {
} Comparer<JsonUInt> comparer(lhs);
return accept(comparer);
CompareResult visitBoolean(bool lhs) { }
Comparer<bool> comparer(lhs);
return accept(comparer); CompareResult visitBoolean(bool lhs) {
} Comparer<bool> comparer(lhs);
return accept(comparer);
CompareResult visitNull() { }
NullComparer comparer;
return accept(comparer); CompareResult visitNull() {
} NullComparer comparer;
return accept(comparer);
private: }
template <typename TComparer>
CompareResult accept(TComparer& comparer) { private:
CompareResult reversedResult = variantAccept(rhs, comparer); template <typename TComparer>
switch (reversedResult) { CompareResult accept(TComparer & comparer) {
case COMPARE_RESULT_GREATER: CompareResult reversedResult = variantAccept(rhs, comparer);
return COMPARE_RESULT_LESS; switch (reversedResult) {
case COMPARE_RESULT_LESS: case COMPARE_RESULT_GREATER:
return COMPARE_RESULT_GREATER; return COMPARE_RESULT_LESS;
default: case COMPARE_RESULT_LESS:
return reversedResult; return COMPARE_RESULT_GREATER;
default:
return reversedResult;
}
} }
}
}; };
template <typename T> template <typename T>
struct Comparer<T, typename enable_if<is_convertible< struct Comparer<T, typename enable_if<is_convertible<T, ArduinoJson::JsonVariantConst>::value>::type> : VariantComparer {
T, ArduinoJson::JsonVariantConst>::value>::type> explicit Comparer(const T & value)
: VariantComparer { : VariantComparer(VariantAttorney::getData(value)) {
explicit Comparer(const T& value) }
: VariantComparer(VariantAttorney::getData(value)) {}
}; };
template <typename T> template <typename T>
CompareResult compare(ArduinoJson::JsonVariantConst lhs, const T& rhs) { CompareResult compare(ArduinoJson::JsonVariantConst lhs, const T & rhs) {
Comparer<T> comparer(rhs); Comparer<T> comparer(rhs);
return variantAccept(VariantAttorney::getData(lhs), comparer); return variantAccept(VariantAttorney::getData(lhs), comparer);
} }
ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -18,6 +18,9 @@ const delay = (ms) => new Promise((res) => setTimeout(res, ms));
const API_ENDPOINT_ROOT = '/api/'; const API_ENDPOINT_ROOT = '/api/';
const REST_ENDPOINT_ROOT = '/rest/'; const REST_ENDPOINT_ROOT = '/rest/';
// network poll
let countWifiScanPoll = 0;
// LOG // LOG
const LOG_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'logSettings'; const LOG_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'logSettings';
log_settings = { log_settings = {
@@ -27,7 +30,7 @@ log_settings = {
}; };
const FETCH_LOG_ENDPOINT = REST_ENDPOINT_ROOT + 'fetchLog'; const FETCH_LOG_ENDPOINT = REST_ENDPOINT_ROOT + 'fetchLog';
const fetch_log = { let fetch_log = {
events: [ events: [
{ {
t: '000+00:00:00.001', t: '000+00:00:00.001',
@@ -1780,26 +1783,26 @@ const emsesp_devicedata_99 = {
}; };
// CUSTOM ENTITIES // CUSTOM ENTITIES
let emsesp_entities = [ let emsesp_entities = {
// entities: [] // entities: []
// entities: [ entities: [
{ {
id: 0, id: 0,
device_id: 8, device_id: 8,
type_id: 24, type_id: 24,
offset: 0, offset: 0,
factor: 1, factor: 1,
name: 'boiler_flowtemp', name: 'boiler_flowtemp',
uom: 1, uom: 1,
value_type: 1, value_type: 1,
writeable: true writeable: true
} }
]; ]
};
// SCHEDULE // SCHEDULE
let emsesp_schedule = let emsesp_schedule = {
// schedule: [ schedule: [
[
{ {
id: 1, id: 1,
active: true, active: true,
@@ -1836,7 +1839,8 @@ let emsesp_schedule =
value: '', value: '',
name: 'auto_restart' name: 'auto_restart'
} }
]; ]
};
// CUSTOMIZATIONS // CUSTOMIZATIONS
const emsesp_deviceentities_1 = [{}]; const emsesp_deviceentities_1 = [{}];
@@ -1993,16 +1997,13 @@ const emsesp_deviceentities_4 = [
]; ];
// LOG // LOG
rest_server.get(FETCH_LOG_ENDPOINT, (req, res) => { rest_server.post(FETCH_LOG_ENDPOINT, (req, res) => {
const encoded = msgpack.encode(fetch_log); console.log('command: fetchLog');
console.log('fetchlog'); res.sendStatus(200);
res.write(encoded, 'binary');
res.end(null, 'binary');
}); });
rest_server.get(LOG_SETTINGS_ENDPOINT, (req, res) => { rest_server.get(LOG_SETTINGS_ENDPOINT, (req, res) => {
res.json(log_settings); res.json(log_settings);
}); });
// TODO do we need to send back here a res.SendStatus(200) ?
rest_server.post(LOG_SETTINGS_ENDPOINT, (req, res) => { rest_server.post(LOG_SETTINGS_ENDPOINT, (req, res) => {
log_settings = req.body; log_settings = req.body;
console.log(JSON.stringify(log_settings)); console.log(JSON.stringify(log_settings));
@@ -2019,14 +2020,21 @@ rest_server.get(NETWORK_SETTINGS_ENDPOINT, (req, res) => {
rest_server.post(NETWORK_SETTINGS_ENDPOINT, (req, res) => { rest_server.post(NETWORK_SETTINGS_ENDPOINT, (req, res) => {
network_settings = req.body; network_settings = req.body;
console.log(JSON.stringify(network_settings)); console.log(JSON.stringify(network_settings));
// TODO do we need to send back here a res.SendStatus(200) ?
res.sendStatus(200); res.sendStatus(200);
}); });
rest_server.get(LIST_NETWORKS_ENDPOINT, (req, res) => { rest_server.get(LIST_NETWORKS_ENDPOINT, (req, res) => {
countWifiScanPoll = 0; // stop the poll
res.json(list_networks); res.json(list_networks);
}); });
rest_server.get(SCAN_NETWORKS_ENDPOINT, (req, res) => { rest_server.get(SCAN_NETWORKS_ENDPOINT, (req, res) => {
res.sendStatus(202); // reboot required console.log('scan networks');
if (countWifiScanPoll++ === 2) {
console.log('done, have list');
res.sendStatus(200); // ready to send list
} else {
console.log('...waiting #' + countWifiScanPoll);
res.sendStatus(202); // waiting....
}
}); });
// AP // AP
@@ -2050,8 +2058,7 @@ rest_server.get(OTA_SETTINGS_ENDPOINT, (req, res) => {
rest_server.post(OTA_SETTINGS_ENDPOINT, (req, res) => { rest_server.post(OTA_SETTINGS_ENDPOINT, (req, res) => {
ota_settings = req.body; ota_settings = req.body;
console.log(JSON.stringify(ota_settings)); console.log(JSON.stringify(ota_settings));
res.json(ota_settings); res.sendStatus(200);
// TODO do we need to send back a res.sendStatus(200); ?
}); });
// MQTT // MQTT
@@ -2061,8 +2068,7 @@ rest_server.get(MQTT_SETTINGS_ENDPOINT, (req, res) => {
rest_server.post(MQTT_SETTINGS_ENDPOINT, (req, res) => { rest_server.post(MQTT_SETTINGS_ENDPOINT, (req, res) => {
mqtt_settings = req.body; mqtt_settings = req.body;
console.log(JSON.stringify(mqtt_settings)); console.log(JSON.stringify(mqtt_settings));
res.json(mqtt_settings); res.sendStatus(200);
// TODO do we need to send back a res.sendStatus(200); ?
}); });
rest_server.get(MQTT_STATUS_ENDPOINT, (req, res) => { rest_server.get(MQTT_STATUS_ENDPOINT, (req, res) => {
res.json(mqtt_status); res.json(mqtt_status);
@@ -2075,9 +2081,7 @@ rest_server.get(NTP_SETTINGS_ENDPOINT, (req, res) => {
rest_server.post(NTP_SETTINGS_ENDPOINT, (req, res) => { rest_server.post(NTP_SETTINGS_ENDPOINT, (req, res) => {
ntp_settings = req.body; ntp_settings = req.body;
console.log(JSON.stringify(ntp_settings)); console.log(JSON.stringify(ntp_settings));
// TODO do we need to send back a res.sendStatus(200); ? res.sendStatus(200);
res.json(ntp_settings);
}); });
rest_server.get(NTP_STATUS_ENDPOINT, (req, res) => { rest_server.get(NTP_STATUS_ENDPOINT, (req, res) => {
res.json(ntp_status); res.json(ntp_status);
@@ -2096,9 +2100,7 @@ rest_server.get(SECURITY_SETTINGS_ENDPOINT, (req, res) => {
rest_server.post(SECURITY_SETTINGS_ENDPOINT, (req, res) => { rest_server.post(SECURITY_SETTINGS_ENDPOINT, (req, res) => {
security_settings = req.body; security_settings = req.body;
console.log(JSON.stringify(security_settings)); console.log(JSON.stringify(security_settings));
// TODO do we need to send back a res.sendStatus(200); ? res.sendStatus(200);
res.json(security_settings);
}); });
rest_server.get(FEATURES_ENDPOINT, (req, res) => { rest_server.get(FEATURES_ENDPOINT, (req, res) => {
res.json(features); res.json(features);
@@ -2118,9 +2120,7 @@ rest_server.post(UPLOAD_FILE_ENDPOINT, (req, res) => {
}); });
rest_server.post(SIGN_IN_ENDPOINT, (req, res) => { rest_server.post(SIGN_IN_ENDPOINT, (req, res) => {
console.log('Signed in as ' + req.body.username); console.log('Signed in as ' + req.body.username);
// TODO do we need to send back a res.sendStatus(200); ? res.json(signin); // watch out, this has a return value
res.json(signin);
}); });
rest_server.get(GENERATE_TOKEN_ENDPOINT, (req, res) => { rest_server.get(GENERATE_TOKEN_ENDPOINT, (req, res) => {
res.json(generate_token); res.json(generate_token);
@@ -2693,7 +2693,7 @@ rest_server.get(SCHEDULE_ENDPOINT, (req, res) => {
const ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'entities'; const ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'entities';
rest_server.get(ENTITIES_ENDPOINT, (req, res) => { rest_server.get(ENTITIES_ENDPOINT, (req, res) => {
console.log('Sending Entities data'); console.log('Sending Custom Entities data');
res.json(emsesp_entities); res.json(emsesp_entities);
}); });
@@ -2734,5 +2734,5 @@ rest_server.get(ES_LOG_ENDPOINT, function (req, res) {
log_index = 0; log_index = 0;
} }
fetch_log.events.push(data); // append to buffer fetch_log.events.push(data); // append to buffer
}, 1000); }, 3000);
}); });

View File

@@ -1,7 +1,7 @@
/* /*
* EMS-ESP - https://github.com/emsesp/EMS-ESP * EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2023 Paul Derbyshire * Copyright 2020-2023 Paul Derbyshire
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
@@ -24,25 +24,16 @@ namespace emsesp {
WebLogService::WebLogService(AsyncWebServer * server, SecurityManager * securityManager) WebLogService::WebLogService(AsyncWebServer * server, SecurityManager * securityManager)
: events_(EVENT_SOURCE_LOG_PATH) : events_(EVENT_SOURCE_LOG_PATH)
, setValues_(LOG_SETTINGS_PATH, std::bind(&WebLogService::setValues, this, _1, _2), 256) { // for POSTS , setValues_(LOG_SETTINGS_PATH, std::bind(&WebLogService::setValues, this, _1, _2), 256) {
events_.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN)); events_.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN));
server->addHandler(&events_); server->on(LOG_SETTINGS_PATH, HTTP_GET, std::bind(&WebLogService::getValues, this, _1)); // get settings
server->on(EVENT_SOURCE_LOG_PATH, HTTP_GET, std::bind(&WebLogService::forbidden, this, _1));
// for bring back the whole log // for bring back the whole log - is a command, hence a POST
server->on(FETCH_LOG_PATH, HTTP_GET, std::bind(&WebLogService::fetchLog, this, _1)); server->on(FETCH_LOG_PATH, HTTP_POST, std::bind(&WebLogService::fetchLog, this, _1));
// get when page is loaded
server->on(LOG_SETTINGS_PATH, HTTP_GET, std::bind(&WebLogService::getValues, this, _1));
// for setting a level
server->addHandler(&setValues_); server->addHandler(&setValues_);
} server->addHandler(&events_);
void WebLogService::forbidden(AsyncWebServerRequest * request) {
request->send(403);
} }
// start the log service with INFO level // start the log service with INFO level
@@ -211,6 +202,7 @@ void WebLogService::transmit(const QueuedLogMessage & message) {
} }
// send the complete log buffer to the API, not filtering on log level // send the complete log buffer to the API, not filtering on log level
// done by resetting the pointer
void WebLogService::fetchLog(AsyncWebServerRequest * request) { void WebLogService::fetchLog(AsyncWebServerRequest * request) {
log_message_id_tail_ = 0; log_message_id_tail_ = 0;
request->send(200); request->send(200);
@@ -224,6 +216,8 @@ void WebLogService::setValues(AsyncWebServerRequest * request, JsonVariant & jso
auto && body = json.as<JsonObject>(); auto && body = json.as<JsonObject>();
// TODO refactor into one load and one save
uuid::log::Level level = body["level"]; uuid::log::Level level = body["level"];
log_level(level); log_level(level);

View File

@@ -58,7 +58,6 @@ class WebLogService : public uuid::log::Handler {
const std::shared_ptr<const uuid::log::Message> content_; // Log message content const std::shared_ptr<const uuid::log::Message> content_; // Log message content
}; };
void forbidden(AsyncWebServerRequest * request);
void transmit(const QueuedLogMessage & message); void transmit(const QueuedLogMessage & message);
void fetchLog(AsyncWebServerRequest * request); void fetchLog(AsyncWebServerRequest * request);
void getValues(AsyncWebServerRequest * request); void getValues(AsyncWebServerRequest * request);

View File

@@ -50,7 +50,7 @@ void WebScheduler::read(WebScheduler & webScheduler, JsonObject & root) {
} }
} }
// call on initialization and also when the Scheduile web page is updated // call on initialization and also when the Scheduile web page is saved
// this loads the data into the internal class // this loads the data into the internal class
StateUpdateResult WebScheduler::update(JsonObject & root, WebScheduler & webScheduler) { StateUpdateResult WebScheduler::update(JsonObject & root, WebScheduler & webScheduler) {
#ifdef EMSESP_STANDALONE #ifdef EMSESP_STANDALONE