api refactoring

This commit is contained in:
proddy
2024-08-09 14:35:33 +02:00
parent 93ed502e57
commit 251b0ea287
24 changed files with 134 additions and 150 deletions

View File

@@ -1,5 +1,5 @@
import { useContext, useEffect } from 'react';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import { Navigate, Route, Routes } from 'react-router-dom';
import { toast } from 'react-toastify';
import AuthenticatedRouting from 'AuthenticatedRouting';

View File

@@ -4,9 +4,7 @@ import { toast } from 'react-toastify';
import ForwardIcon from '@mui/icons-material/Forward';
import { Box, Button, Paper, Typography } from '@mui/material';
import * as AuthenticationApi from 'api/authentication';
import { PROJECT_NAME } from 'api/env';
import * as AuthenticationApi from 'components/routing/authentication';
import { useRequest } from 'alova/client';
import type { ValidateFieldsError } from 'async-validator';
import {
@@ -15,6 +13,7 @@ import {
ValidatedTextField
} from 'components';
import { AuthenticationContext } from 'contexts/authentication';
import { PROJECT_NAME } from 'env';
import { useI18nContext } from 'i18n/i18n-react';
import type { SignInRequest } from 'types';
import { onEnterCallback, updateValue } from 'utils';

View File

@@ -17,7 +17,7 @@ import type {
Settings,
WriteAnalogSensor,
WriteTemperatureSensor
} from './types';
} from '../app/main/types';
// DashboardDevices
export const readCoreData = () => alovaInstance.Get<CoreData>(`/rest/coreData`);

View File

@@ -2,7 +2,7 @@ import { xhrRequestAdapter } from '@alova/adapter-xhr';
import { createAlova } from 'alova';
import ReactHook from 'alova/react';
import { unpack } from '../api/unpack';
import { unpack } from './unpack';
export const ACCESS_TOKEN = 'access_token';

View File

@@ -29,8 +29,8 @@ import {
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { readCustomEntities, writeCustomEntities } from '../../api/app';
import SettingsCustomEntitiesDialog from './CustomEntitiesDialog';
import { readCustomEntities, writeCustomEntities } from './api';
import { DeviceValueTypeNames, DeviceValueUOM_s } from './types';
import type { Entities, EntityItem } from './types';
import { entityItemValidation } from './validators';

View File

@@ -26,7 +26,7 @@ import {
Typography
} from '@mui/material';
import * as SystemApi from 'api/system';
import { restart } from 'api/system';
import {
Body,
@@ -50,7 +50,13 @@ import {
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api';
import {
readDeviceEntities,
readDevices,
resetCustomizations,
writeCustomizationEntities,
writeDeviceName
} from '../../api/app';
import SettingsCustomizationsDialog from './CustomizationsDialog';
import EntityMaskToggle from './EntityMaskToggle';
import OptionIcon from './OptionIcon';
@@ -77,7 +83,7 @@ const Customizations = () => {
useLayoutTitle(LL.CUSTOMIZATIONS());
// fetch devices first
const { data: devices, send: fetchDevices } = useRequest(EMSESP.readDevices);
const { data: devices, send: fetchDevices } = useRequest(readDevices);
const [selectedDevice, setSelectedDevice] = useState<number>(
Number(useLocation().state) || -1
@@ -86,27 +92,26 @@ const Customizations = () => {
useState<string>(''); // needed for API URL
const [selectedDeviceName, setSelectedDeviceName] = useState<string>('');
const { send: resetCustomizations } = useRequest(EMSESP.resetCustomizations(), {
const { send: sendResetCustomizations } = useRequest(resetCustomizations(), {
immediate: false
});
const { send: writeDeviceName } = useRequest(
(data: { id: number; name: string }) => EMSESP.writeDeviceName(data),
const { send: sendDeviceName } = useRequest(
(data: { id: number; name: string }) => writeDeviceName(data),
{
immediate: false
}
);
const { send: writeCustomizationEntities } = useRequest(
(data: { id: number; entity_ids: string[] }) =>
EMSESP.writeCustomizationEntities(data),
const { send: sendCustomizationEntities } = useRequest(
(data: { id: number; entity_ids: string[] }) => writeCustomizationEntities(data),
{
immediate: false
}
);
const { send: readDeviceEntities } = useRequest(
(data: number) => EMSESP.readDeviceEntities(data),
const { send: sendDeviceEntities } = useRequest(
(data: number) => readDeviceEntities(data),
{
initialData: [],
immediate: false
@@ -127,7 +132,7 @@ const Customizations = () => {
);
};
const { send: restartCommand } = useRequest(SystemApi.restart(), {
const { send: sendRestart } = useRequest(restart(), {
immediate: false
});
@@ -228,7 +233,7 @@ const Customizations = () => {
useEffect(() => {
if (devices && selectedDevice !== -1) {
void readDeviceEntities(selectedDevice);
void sendDeviceEntities(selectedDevice);
const id = devices.devices.findIndex((d) => d.i === selectedDevice);
if (id === -1) {
setSelectedDevice(-1);
@@ -242,8 +247,8 @@ const Customizations = () => {
}
}, [devices, selectedDevice]);
const restart = async () => {
await restartCommand().catch((error: Error) => {
const doRestart = async () => {
await sendRestart().catch((error: Error) => {
toast.error(error.message);
});
setRestarting(true);
@@ -324,7 +329,7 @@ const Customizations = () => {
const resetCustomization = async () => {
try {
await resetCustomizations();
await sendResetCustomizations();
toast.info(LL.CUSTOMIZATIONS_RESTART());
} catch (error) {
toast.error((error as Error).message);
@@ -384,7 +389,7 @@ const Customizations = () => {
return;
}
await writeCustomizationEntities({
await sendCustomizationEntities({
id: selectedDevice,
entity_ids: masked_entities
}).catch((error: Error) => {
@@ -399,7 +404,7 @@ const Customizations = () => {
};
const renameDevice = async () => {
await writeDeviceName({ id: selectedDevice, name: selectedDeviceName })
await sendDeviceName({ id: selectedDevice, name: selectedDeviceName })
.then(() => {
toast.success(LL.UPDATED_OF(LL.NAME(1)));
})
@@ -673,7 +678,7 @@ const Customizations = () => {
startIcon={<PowerSettingsNewIcon />}
variant="contained"
color="error"
onClick={restart}
onClick={doRestart}
>
{LL.RESTART()}
</Button>
@@ -688,7 +693,7 @@ const Customizations = () => {
startIcon={<CancelIcon />}
variant="outlined"
color="secondary"
onClick={() => devices && readDeviceEntities(selectedDevice)}
onClick={() => devices && sendDeviceEntities(selectedDevice)}
>
{LL.CANCEL()}
</Button>

View File

@@ -61,7 +61,7 @@ import { ButtonRow, MessageBox, SectionContent, useLayoutTitle } from 'component
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api';
import { readCoreData, readDeviceData, writeDeviceValue } from '../../api/app';
import DeviceIcon from './DeviceIcon';
import DashboardDevicesDialog from './DevicesDialog';
import { formatValue } from './deviceValue';
@@ -84,18 +84,15 @@ const Devices = () => {
useLayoutTitle(LL.DEVICES());
const { data: coreData, send: readCoreData } = useRequest(
() => EMSESP.readCoreData(),
{
initialData: {
connected: true,
devices: []
}
const { data: coreData, send: sendCoreData } = useRequest(() => readCoreData(), {
initialData: {
connected: true,
devices: []
}
);
});
const { data: deviceData, send: readDeviceData } = useRequest(
(id: number) => EMSESP.readDeviceData(id),
const { data: deviceData, send: sendDeviceData } = useRequest(
(id: number) => readDeviceData(id),
{
initialData: {
data: []
@@ -104,8 +101,8 @@ const Devices = () => {
}
);
const { loading: submitting, send: writeDeviceValue } = useRequest(
(data: { id: number; c: string; v: unknown }) => EMSESP.writeDeviceValue(data),
const { loading: submitting, send: sendDeviceValue } = useRequest(
(data: { id: number; c: string; v: unknown }) => writeDeviceValue(data),
{
immediate: false
}
@@ -287,7 +284,7 @@ const Devices = () => {
async function onSelectChange(action: Action, state: State) {
setSelectedDevice(state.id as number);
if (action.type === 'ADD_BY_ID_EXCLUSIVELY') {
await readDeviceData(state.id as number);
await sendDeviceData(state.id as number);
}
}
@@ -322,7 +319,7 @@ const Devices = () => {
const refreshData = () => {
if (!deviceValueDialogOpen) {
selectedDevice ? void readDeviceData(selectedDevice) : void readCoreData();
selectedDevice ? void sendDeviceData(selectedDevice) : void sendCoreData();
}
};
@@ -444,7 +441,7 @@ const Devices = () => {
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
const id = Number(device_select.state.id);
await writeDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v })
await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v })
.then(() => {
toast.success(LL.WRITE_CMD_SENT());
})
@@ -453,7 +450,7 @@ const Devices = () => {
})
.finally(async () => {
setDeviceValueDialogOpen(false);
await readDeviceData(id);
await sendDeviceData(id);
setSelectedDeviceValue(undefined);
});
};

View File

@@ -17,20 +17,20 @@ import {
Typography
} from '@mui/material';
import * as SystemApi from 'api/system';
import { readSystemStatus } from 'api/system';
import * as EMSESP from 'app/main/api';
import { useRequest } from 'alova/client';
import { SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { API } from '../../api/app';
import type { APIcall } from './types';
const Help = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.HELP_OF(''));
const { send: getAPI } = useRequest((data: APIcall) => EMSESP.API(data), {
const { send: getAPI } = useRequest((data: APIcall) => API(data), {
immediate: false
}).onSuccess((event) => {
const anchor = document.createElement('a');
@@ -47,7 +47,7 @@ const Help = () => {
toast.info(LL.DOWNLOAD_SUCCESSFUL());
});
const { data, loading } = useRequest(SystemApi.readSystemStatus);
const { data, loading } = useRequest(readSystemStatus);
const callAPI = async (device: string, entity: string) => {
await getAPI({ device, entity, id: 0 }).catch((error: Error) => {

View File

@@ -27,8 +27,8 @@ import {
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { readModules, writeModules } from '../../api/app';
import ModulesDialog from './ModulesDialog';
import { readModules, writeModules } from './api';
import type { ModuleItem, Modules } from './types';
const Modules = () => {

View File

@@ -28,8 +28,8 @@ import {
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { readSchedule, writeSchedule } from '../../api/app';
import SettingsSchedulerDialog from './SchedulerDialog';
import { readSchedule, writeSchedule } from './api';
import { ScheduleFlag } from './types';
import type { Schedule, ScheduleItem } from './types';
import { schedulerItemValidation } from './validators';

View File

@@ -25,7 +25,11 @@ import { ButtonRow, SectionContent, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api';
import {
readSensorData,
writeAnalogSensor,
writeTemperatureSensor
} from '../../api/app';
import DashboardSensorsAnalogDialog from './SensorsAnalogDialog';
import DashboardSensorsTemperatureDialog from './SensorsTemperatureDialog';
import {
@@ -56,8 +60,8 @@ const Sensors = () => {
const [analogDialogOpen, setAnalogDialogOpen] = useState<boolean>(false);
const [creating, setCreating] = useState<boolean>(false);
const { data: sensorData, send: fetchSensorData } = useRequest(
() => EMSESP.readSensorData(),
const { data: sensorData, send: sendSensorData } = useRequest(
() => readSensorData(),
{
initialData: {
ts: [],
@@ -68,15 +72,15 @@ const Sensors = () => {
}
);
const { send: writeTemperatureSensor } = useRequest(
(data: WriteTemperatureSensor) => EMSESP.writeTemperatureSensor(data),
const { send: sendTemperatureSensor } = useRequest(
(data: WriteTemperatureSensor) => writeTemperatureSensor(data),
{
immediate: false
}
);
const { send: writeAnalogSensor } = useRequest(
(data: WriteAnalogSensor) => EMSESP.writeAnalogSensor(data),
const { send: sendAnalogSensor } = useRequest(
(data: WriteAnalogSensor) => writeAnalogSensor(data),
{
immediate: false
}
@@ -195,7 +199,7 @@ const Sensors = () => {
);
useEffect(() => {
const timer = setInterval(() => fetchSensorData(), 30000);
const timer = setInterval(() => sendSensorData(), 30000);
return () => {
clearInterval(timer);
};
@@ -265,7 +269,7 @@ const Sensors = () => {
};
const onTemperatureDialogSave = async (ts: TemperatureSensor) => {
await writeTemperatureSensor({ id: ts.id, name: ts.n, offset: ts.o })
await sendTemperatureSensor({ id: ts.id, name: ts.n, offset: ts.o })
.then(() => {
toast.success(LL.UPDATED_OF(LL.SENSOR(1)));
})
@@ -275,7 +279,7 @@ const Sensors = () => {
.finally(async () => {
setTemperatureDialogOpen(false);
setSelectedTemperatureSensor(undefined);
await fetchSensorData();
await sendSensorData();
});
};
@@ -310,7 +314,7 @@ const Sensors = () => {
};
const onAnalogDialogSave = async (as: AnalogSensor) => {
await writeAnalogSensor({
await sendAnalogSensor({
id: as.id,
gpio: as.g,
name: as.n,
@@ -329,7 +333,7 @@ const Sensors = () => {
.finally(async () => {
setAnalogDialogOpen(false);
setSelectedAnalogSensor(undefined);
await fetchSensorData();
await sendSensorData();
});
};
@@ -503,7 +507,7 @@ const Sensors = () => {
startIcon={<RefreshIcon />}
variant="outlined"
color="secondary"
onClick={fetchSensorData}
onClick={sendSensorData}
>
{LL.REFRESH()}
</Button>

View File

@@ -18,7 +18,6 @@ import {
useLayoutTitle
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { range } from 'lodash-es';
import type { APSettingsType } from 'types';
import { APProvisionMode } from 'types';
import { numberValue, updateValueDirty, useRest } from 'utils';
@@ -73,6 +72,11 @@ const APSettings = () => {
}
};
// no lodash - https://asleepace.com/blog/typescript-range-without-a-loop/
function range(a: number, b: number): number[] {
return a < b ? [a, ...range(a + 1, b)] : [b];
}
return (
<>
<ValidatedTextField

View File

@@ -16,7 +16,7 @@ import {
Typography
} from '@mui/material';
import * as SystemApi from 'api/system';
import { readHardwareStatus, restart } from 'api/system';
import { useRequest } from 'alova/client';
import RestartMonitor from 'app/status/RestartMonitor';
@@ -35,7 +35,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValueDirty, useRest } from 'utils';
import { validate } from 'validators';
import * as EMSESP from '../main/api';
import { getBoardProfile, readSettings, writeSettings } from '../../api/app';
import { BOARD_PROFILES } from '../main/types';
import type { Settings } from '../main/types';
import { createSettingsValidator } from '../main/validators';
@@ -49,7 +49,7 @@ export function boardProfileSelectItems() {
}
const ApplicationSettings = () => {
const { data: hardwareData } = useRequest(SystemApi.readHardwareStatus, {
const { data: hardwareData } = useRequest(readHardwareStatus, {
initialData: { psram: false }
});
@@ -66,8 +66,8 @@ const ApplicationSettings = () => {
errorMessage,
restartNeeded
} = useRest<Settings>({
read: EMSESP.readSettings,
update: EMSESP.writeSettings
read: readSettings,
update: writeSettings
});
const [restarting, setRestarting] = useState<boolean>();
@@ -84,7 +84,7 @@ const ApplicationSettings = () => {
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const { loading: processingBoard, send: readBoardProfile } = useRequest(
(boardProfile: string) => EMSESP.getBoardProfile(boardProfile),
(boardProfile: string) => getBoardProfile(boardProfile),
{
immediate: false
}
@@ -105,7 +105,7 @@ const ApplicationSettings = () => {
});
});
const { send: restartCommand } = useRequest(SystemApi.restart(), {
const { send: restartCommand } = useRequest(restart(), {
immediate: false
});

View File

@@ -5,8 +5,15 @@ import DownloadIcon from '@mui/icons-material/GetApp';
import { Box, Button, Divider, Link, Typography } from '@mui/material';
import * as SystemApi from 'api/system';
import {
API,
getCustomizations,
getEntities,
getSchedule,
getSettings
} from 'api/app';
import { getDevVersion, getStableVersion } from 'api/system';
import * as EMSESP from 'app/main/api';
import { useRequest } from 'alova/client';
import type { APIcall } from 'app/main/types';
import {
@@ -24,31 +31,31 @@ const UploadDownload = () => {
const [restarting, setRestarting] = useState<boolean>();
const [md5, setMd5] = useState<string>();
const { send: getSettings } = useRequest(EMSESP.getSettings(), {
const { send: sendSettings } = useRequest(getSettings(), {
immediate: false
}).onSuccess((event) => {
saveFile(event.data, 'settings.json');
});
const { send: getCustomizations } = useRequest(EMSESP.getCustomizations(), {
const { send: sendCustomizations } = useRequest(getCustomizations(), {
immediate: false
}).onSuccess((event) => {
saveFile(event.data, 'customizations.json');
});
const { send: getEntities } = useRequest(EMSESP.getEntities(), {
const { send: sendEntities } = useRequest(getEntities(), {
immediate: false
}).onSuccess((event) => {
saveFile(event.data, 'entities.json');
});
const { send: getSchedule } = useRequest(EMSESP.getSchedule(), {
const { send: sendSchedule } = useRequest(getSchedule(), {
immediate: false
}).onSuccess((event) => {
saveFile(event.data, 'schedule.json');
});
const { send: getAPI } = useRequest((data: APIcall) => EMSESP.API(data), {
const { send: getAPI } = useRequest((data: APIcall) => API(data), {
immediate: false
}).onSuccess((event) => {
saveFile(
@@ -64,8 +71,8 @@ const UploadDownload = () => {
} = useRequest(SystemApi.readHardwareStatus);
// called immediately to get the latest version, on page load
const { data: latestVersion } = useRequest(SystemApi.getStableVersion);
const { data: latestDevVersion } = useRequest(SystemApi.getDevVersion);
const { data: latestVersion } = useRequest(getStableVersion);
const { data: latestDevVersion } = useRequest(getDevVersion);
const STABLE_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/';
const STABLE_RELNOTES_URL =
@@ -131,25 +138,25 @@ const UploadDownload = () => {
};
const downloadSettings = async () => {
await getSettings().catch((error: Error) => {
await sendSettings().catch((error: Error) => {
toast.error(error.message);
});
};
const downloadCustomizations = async () => {
await getCustomizations().catch((error: Error) => {
await sendCustomizations().catch((error: Error) => {
toast.error(error.message);
});
};
const downloadEntities = async () => {
await getEntities().catch((error: Error) => {
await sendEntities().catch((error: Error) => {
toast.error(error.message);
});
};
const downloadSchedule = async () => {
await getSchedule().catch((error: Error) => {
await sendSchedule().catch((error: Error) => {
toast.error(error.message);
});
};

View File

@@ -18,7 +18,7 @@ import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'component
import { useI18nContext } from 'i18n/i18n-react';
import type { Translation } from 'i18n/i18n-types';
import * as EMSESP from '../main/api';
import * as EMSESP from '../../api/app';
import type { Stat } from '../main/types';
const SystemActivity = () => {

View File

@@ -4,8 +4,7 @@ import { useLocation } from 'react-router-dom';
import { Box, Toolbar } from '@mui/material';
import { PROJECT_NAME } from 'api/env';
import { PROJECT_NAME } from 'env';
import type { RequiredChildrenProps } from 'utils';
import LayoutAppBar from './LayoutAppBar';

View File

@@ -1,6 +1,6 @@
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
import { PROJECT_NAME } from 'api/env';
import { PROJECT_NAME } from 'env';
import { DRAWER_WIDTH } from './Layout';
import LayoutMenu from './LayoutMenu';

View File

@@ -2,8 +2,7 @@ import { useContext, useEffect } from 'react';
import type { FC } from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import { storeLoginRedirect } from 'api/authentication';
import { storeLoginRedirect } from 'components/routing/authentication';
import type { AuthenticatedContextValue } from 'contexts/authentication/context';
import {
AuthenticatedContext,

View File

@@ -2,8 +2,7 @@ import { useContext } from 'react';
import type { FC } from 'react';
import { Navigate } from 'react-router-dom';
import * as AuthenticationApi from 'api/authentication';
import { fetchLoginRedirect } from 'components/routing/authentication';
import { AuthenticationContext } from 'contexts/authentication';
import type { RequiredChildrenProps } from 'utils';
@@ -11,7 +10,7 @@ const RequireUnauthenticated: FC<RequiredChildrenProps> = ({ children }) => {
const authenticationContext = useContext(AuthenticationContext);
return authenticationContext.me ? (
<Navigate to={AuthenticationApi.fetchLoginRedirect()} />
<Navigate to={fetchLoginRedirect()} />
) : (
<>{children}</>
);

View File

@@ -4,7 +4,7 @@ import type * as H from 'history';
import { jwtDecode } from 'jwt-decode';
import type { Me, SignInRequest, SignInResponse } from 'types';
import { ACCESS_TOKEN, alovaInstance } from './endpoints';
import { ACCESS_TOKEN, alovaInstance } from '../../api/endpoints';
export const SIGN_IN_PATHNAME = 'loginPathname';
export const SIGN_IN_SEARCH = 'loginSearch';

View File

@@ -3,11 +3,12 @@ import type { FC } from 'react';
import { redirect } from 'react-router-dom';
import { toast } from 'react-toastify';
import * as AuthenticationApi from 'api/authentication';
import { ACCESS_TOKEN } from 'api/endpoints';
import * as AuthenticationApi from 'components/routing/authentication';
import { useRequest } from 'alova/client';
import { LoadingSpinner } from 'components';
import { verifyAuthorization } from 'components/routing/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import type { Me } from 'types';
import type { RequiredChildrenProps } from 'utils';
@@ -20,12 +21,9 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
const [initialized, setInitialized] = useState<boolean>(false);
const [me, setMe] = useState<Me>();
const { send: verifyAuthorization } = useRequest(
AuthenticationApi.verifyAuthorization(),
{
immediate: false
}
);
const { send: sendVerifyAuthorization } = useRequest(verifyAuthorization(), {
immediate: false
});
const signIn = (accessToken: string) => {
try {
@@ -51,7 +49,7 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
const refresh = useCallback(async () => {
const accessToken = AuthenticationApi.getStorage().getItem(ACCESS_TOKEN);
if (accessToken) {
await verifyAuthorization()
await sendVerifyAuthorization()
.then(() => {
setMe(AuthenticationApi.decodeMeJWT(accessToken));
setInitialized(true);