new linting, make sure code is type safe

This commit is contained in:
proddy
2024-04-20 20:46:01 +02:00
parent ae7cd23758
commit 9dc91f2d69
122 changed files with 1194 additions and 2412 deletions

View File

@@ -1,34 +1,36 @@
import { useState } from 'react';
import type { FC } from 'react';
import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import WarningIcon from '@mui/icons-material/Warning';
import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment, TextField } from '@mui/material';
import { useRequest } from 'alova';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { Box, Button, Checkbox, Divider, Grid, InputAdornment, MenuItem, TextField, Typography } from '@mui/material';
import * as EMSESP from './api';
import { BOARD_PROFILES } from './types';
import { createSettingsValidator } from './validators';
import type { Settings } from './types';
import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react';
import * as SystemApi from 'api/system';
import { useRequest } from 'alova';
import type { ValidateFieldsError } from 'async-validator';
import {
SectionContent,
FormLoader,
BlockFormControlLabel,
ValidatedTextField,
ButtonRow,
MessageBox,
BlockNavigation,
ButtonRow,
FormLoader,
MessageBox,
SectionContent,
ValidatedTextField,
useLayoutTitle
} from 'components';
import RestartMonitor from 'framework/system/RestartMonitor';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValueDirty, useRest } from 'utils';
import { validate } from 'validators';
import * as EMSESP from './api';
import { BOARD_PROFILES } from './types';
import type { Settings } from './types';
import { createSettingsValidator } from './validators';
export function boardProfileSelectItems() {
return Object.keys(BOARD_PROFILES).map((code) => (
<MenuItem key={code} value={code}>
@@ -67,7 +69,7 @@ const ApplicationSettings: FC = () => {
loading: processingBoard,
send: readBoardProfile,
onSuccess: onSuccessBoardProfile
} = useRequest((boardProfile) => EMSESP.getBoardProfile(boardProfile), {
} = useRequest((boardProfile: string) => EMSESP.getBoardProfile(boardProfile), {
immediate: false
});
@@ -93,7 +95,7 @@ const ApplicationSettings: FC = () => {
});
const updateBoardProfile = async (board_profile: string) => {
await readBoardProfile(board_profile).catch((error) => {
await readBoardProfile(board_profile).catch((error: Error) => {
toast.error(error.message);
});
};
@@ -109,8 +111,8 @@ const ApplicationSettings: FC = () => {
try {
setFieldErrors(undefined);
await validate(createSettingsValidator(data), data);
} catch (errors: any) {
setFieldErrors(errors);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
} finally {
await saveData();
}
@@ -131,7 +133,7 @@ const ApplicationSettings: FC = () => {
const restart = async () => {
await validateAndSubmit();
await restartCommand().catch((error) => {
await restartCommand().catch((error: Error) => {
toast.error(error.message);
});
setRestarting(true);

View File

@@ -1,28 +1,27 @@
import { useCallback, useState } from 'react';
import type { FC } from 'react';
import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify';
import AddIcon from '@mui/icons-material/Add';
import CancelIcon from '@mui/icons-material/Cancel';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import RefreshIcon from '@mui/icons-material/Refresh';
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 { Box, Button, Typography } from '@mui/material';
import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme';
// eslint-disable-next-line import/named
import { updateState, useRequest } from 'alova';
import { useState, useCallback } from 'react';
import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify';
import SettingsCustomEntitiesDialog from './CustomEntitiesDialog';
import * as EMSESP from './api';
import { DeviceValueTypeNames, DeviceValueUOM_s } from './types';
import { entityItemValidation } from './validators';
import type { EntityItem } from './types';
import type { FC } from 'react';
import { ButtonRow, FormLoader, SectionContent, BlockNavigation, useLayoutTitle } from 'components';
import { BlockNavigation, ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api';
import SettingsCustomEntitiesDialog from './CustomEntitiesDialog';
import { DeviceValueTypeNames, DeviceValueUOM_s } from './types';
import type { EntityItem } from './types';
import { entityItemValidation } from './validators';
const CustomEntities: FC = () => {
const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0);
@@ -42,7 +41,10 @@ const CustomEntities: FC = () => {
force: true
});
const { send: writeEntities } = useRequest((data) => EMSESP.writeCustomEntities(data), { immediate: false });
const { send: writeEntities } = useRequest(
(data: { id: number; entity_ids: string[] }) => EMSESP.writeCustomEntities(data),
{ immediate: false }
);
function hasEntityChanged(ei: EntityItem) {
return (
@@ -139,8 +141,8 @@ const CustomEntities: FC = () => {
.then(() => {
toast.success(LL.ENTITIES_UPDATED());
})
.catch((err) => {
toast.error(err.message);
.catch((error: Error) => {
toast.error(error.message);
})
.finally(async () => {
await fetchEntities();
@@ -167,7 +169,7 @@ const CustomEntities: FC = () => {
const onDialogSave = (updatedItem: EntityItem) => {
setDialogOpen(false);
updateState('entities', (data) => {
updateState('entities', (data: EntityItem[]) => {
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));
@@ -195,12 +197,12 @@ const CustomEntities: FC = () => {
setDialogOpen(true);
};
function formatValue(value: any, uom: number) {
return value === undefined || uom === undefined
function formatValue(value: unknown, uom: number) {
return value === undefined
? ''
: typeof value === 'number'
? new Intl.NumberFormat().format(value) + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom])
: value;
: (value as string);
}
function showHex(value: number, digit: number) {
@@ -214,7 +216,7 @@ const CustomEntities: FC = () => {
return (
<Table data={{ nodes: entities.filter((ei) => !ei.deleted) }} theme={entity_theme} layout={{ custom: true }}>
{(tableList: any) => (
{(tableList: EntityItem[]) => (
<>
<Header>
<HeaderRow>

View File

@@ -1,3 +1,5 @@
import { useEffect, useState } from 'react';
import AddIcon from '@mui/icons-material/Add';
import CancelIcon from '@mui/icons-material/Cancel';
import DoneIcon from '@mui/icons-material/Done';
@@ -15,29 +17,26 @@ import {
MenuItem,
TextField
} from '@mui/material';
import { useEffect, useState } from 'react';
import { DeviceValueUOM_s, DeviceValueType } from './types';
import type { EntityItem } from './types';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
import { dialogStyle } from 'CustomTheme';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
import { BlockFormControlLabel, ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
type CustomEntitiesDialogProps = {
import { DeviceValueType, DeviceValueUOM_s } from './types';
import type { EntityItem } from './types';
interface CustomEntitiesDialogProps {
open: boolean;
creating: boolean;
onClose: () => void;
onSave: (ei: EntityItem) => void;
selectedItem: EntityItem;
validator: Schema;
};
}
const CustomEntitiesDialog = ({
open,
@@ -80,8 +79,8 @@ const CustomEntitiesDialog = ({
editItem.type_id = parseInt(editItem.type_id, 16);
}
onSave(editItem);
} catch (errors: any) {
setFieldErrors(errors);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
}
};

View File

@@ -1,46 +1,46 @@
import { useCallback, useEffect, useState } from 'react';
import type { FC } from 'react';
import { useBlocker, useLocation } from 'react-router-dom';
import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import SearchIcon from '@mui/icons-material/Search';
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
import WarningIcon from '@mui/icons-material/Warning';
import {
Button,
Typography,
Box,
MenuItem,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Grid,
InputAdornment,
Link,
MenuItem,
TextField,
ToggleButton,
ToggleButtonGroup,
Grid,
TextField,
Link,
InputAdornment
Typography
} 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 { useRequest } from 'alova';
import { useState, useEffect, useCallback } from 'react';
import { useBlocker, useLocation } from 'react-router-dom';
import { toast } from 'react-toastify';
import * as SystemApi from 'api/system';
import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme';
import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova';
import { BlockNavigation, ButtonRow, MessageBox, SectionContent, useLayoutTitle } from 'components';
import RestartMonitor from 'framework/system/RestartMonitor';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api';
import SettingsCustomizationDialog from './CustomizationDialog';
import EntityMaskToggle from './EntityMaskToggle';
import OptionIcon from './OptionIcon';
import * as EMSESP from './api';
import { DeviceEntityMask } from './types';
import type { DeviceShort, DeviceEntity } from './types';
import type { FC } from 'react';
import { dialogStyle } from 'CustomTheme';
import * as SystemApi from 'api/system';
import { ButtonRow, SectionContent, MessageBox, BlockNavigation, useLayoutTitle } from 'components';
import RestartMonitor from 'framework/system/RestartMonitor';
import { useI18nContext } from 'i18n/i18n-react';
import type { DeviceEntity, DeviceShort } from './types';
export const APIURL = window.location.origin + '/api/';
@@ -63,22 +63,27 @@ const Customization: FC = () => {
// fetch devices first
const { data: devices } = useRequest(EMSESP.readDevices);
// const { state } = useLocation();
const [selectedDevice, setSelectedDevice] = useState<number>(useLocation().state || -1);
const [selectedDevice, setSelectedDevice] = useState<number>(Number(useLocation().state) || -1);
const [selectedDeviceName, setSelectedDeviceName] = useState<string>('');
const { send: resetCustomizations } = useRequest(EMSESP.resetCustomizations(), {
immediate: false
});
const { send: writeCustomizationEntities } = useRequest((data) => EMSESP.writeCustomizationEntities(data), {
immediate: false
});
const { send: writeCustomizationEntities } = useRequest(
(data: { id: number; entity_ids: string[] }) => EMSESP.writeCustomizationEntities(data),
{
immediate: false
}
);
const { send: readDeviceEntities, onSuccess: onSuccess } = useRequest((data) => EMSESP.readDeviceEntities(data), {
initialData: [],
immediate: false
});
const { send: readDeviceEntities, onSuccess: onSuccess } = useRequest(
(data: number) => EMSESP.readDeviceEntities(data),
{
initialData: [],
immediate: false
}
);
const setOriginalSettings = (data: DeviceEntity[]) => {
setDeviceEntities(data.map((de) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })));
@@ -195,17 +200,16 @@ const Customization: FC = () => {
setRestartNeeded(false);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [devices, selectedDevice]);
const restart = async () => {
await restartCommand().catch((error) => {
await restartCommand().catch((error: Error) => {
toast.error(error.message);
});
setRestarting(true);
};
function formatValue(value: any) {
function formatValue(value: unknown) {
if (typeof value === 'number') {
return new Intl.NumberFormat().format(value);
} else if (value === undefined) {
@@ -213,7 +217,7 @@ const Customization: FC = () => {
} else if (typeof value === 'boolean') {
return value ? 'true' : 'false';
}
return value;
return value as string;
}
const formatName = (de: DeviceEntity, withShortname: boolean) =>
@@ -273,7 +277,7 @@ const Customization: FC = () => {
await resetCustomizations();
toast.info(LL.CUSTOMIZATIONS_RESTART());
} catch (error) {
toast.error(error.message);
toast.error((error as Error).message);
} finally {
setConfirmReset(false);
}
@@ -326,7 +330,7 @@ const Customization: FC = () => {
return;
}
await writeCustomizationEntities({ id: selectedDevice, entity_ids: masked_entities }).catch((error) => {
await writeCustomizationEntities({ id: selectedDevice, entity_ids: masked_entities }).catch((error: Error) => {
if (error.message === 'Reboot required') {
setRestartNeeded(true);
} else {
@@ -402,7 +406,7 @@ const Customization: FC = () => {
size="small"
color="secondary"
value={getMaskString(selectedFilters)}
onChange={(event, mask) => {
onChange={(event, mask: string[]) => {
setSelectedFilters(getMaskNumber(mask));
}}
>
@@ -456,7 +460,7 @@ const Customization: FC = () => {
</Grid>
</Grid>
<Table data={{ nodes: shown_data }} theme={entities_theme} layout={{ custom: true }}>
{(tableList: any) => (
{(tableList: DeviceEntity[]) => (
<>
<Header>
<HeaderRow>

View File

@@ -1,7 +1,8 @@
import { useEffect, useState } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import CloseIcon from '@mui/icons-material/Close';
import DoneIcon from '@mui/icons-material/Done';
import {
Box,
Button,
@@ -13,23 +14,21 @@ import {
TextField,
Typography
} from '@mui/material';
import { useEffect, useState } from 'react';
import { dialogStyle } from 'CustomTheme';
import { useI18nContext } from 'i18n/i18n-react';
import { updateValue } from 'utils';
import EntityMaskToggle from './EntityMaskToggle';
import { DeviceEntityMask } from './types';
import type { DeviceEntity } from './types';
import { dialogStyle } from 'CustomTheme';
import { useI18nContext } from 'i18n/i18n-react';
import { updateValue } from 'utils';
type SettingsCustomizationDialogProps = {
interface SettingsCustomizationDialogProps {
open: boolean;
onClose: () => void;
onSave: (di: DeviceEntity) => void;
selectedItem: DeviceEntity;
};
}
const CustomizationDialog = ({ open, onClose, onSave, selectedItem }: SettingsCustomizationDialogProps) => {
const { LL } = useI18nContext();

View File

@@ -1,21 +1,22 @@
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
import { AiOutlineControl, AiOutlineGateway, AiOutlineAlert } from 'react-icons/ai';
import type { FC } from 'react';
import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/ai';
import { CgSmartHomeBoiler } from 'react-icons/cg';
import { FaSolarPanel } from 'react-icons/fa';
import { GiHeatHaze, GiTap } from 'react-icons/gi';
import { MdThermostatAuto, MdOutlineSensors, MdOutlineDevices, MdOutlinePool } from 'react-icons/md';
import { MdOutlineDevices, MdOutlinePool, MdOutlineSensors, MdThermostatAuto } from 'react-icons/md';
import { TiFlowSwitch } from 'react-icons/ti';
import { VscVmConnect } from 'react-icons/vsc';
import { DeviceType } from './types';
import type { FC } from 'react';
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
import { DeviceType } from './types';
interface DeviceIconProps {
type_id: number;
}
const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
switch (type_id) {
switch (type_id as DeviceType) {
case DeviceType.TEMPERATURESENSOR:
case DeviceType.ANALOGSENSOR:
return <MdOutlineSensors />;

View File

@@ -1,3 +1,9 @@
import { useCallback, useContext, useEffect, useLayoutEffect, useState } from 'react';
import type { FC } from 'react';
import { IconContext } from 'react-icons';
import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify';
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
import EditIcon from '@mui/icons-material/Edit';
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
@@ -12,48 +18,40 @@ import RefreshIcon from '@mui/icons-material/Refresh';
import StarIcon from '@mui/icons-material/Star';
import StarBorderOutlinedIcon from '@mui/icons-material/StarBorderOutlined';
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
import {
Box,
Button,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
DialogContent,
DialogTitle,
Grid,
IconButton,
List,
ListItem,
ListItemText,
Box,
Grid,
Typography
} from '@mui/material';
import { useRowSelect } from '@table-library/react-table-library/select';
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
import { SortToggleType, useSort } from '@table-library/react-table-library/sort';
import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme';
import { useRequest } from 'alova';
import { useState, useEffect, useCallback, useLayoutEffect, useContext } from 'react';
import { IconContext } from 'react-icons';
import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify';
import DeviceIcon from './DeviceIcon';
import DashboardDevicesDialog from './DevicesDialog';
import * as EMSESP from './api';
import { formatValue } from './deviceValue';
import { DeviceValueUOM_s, DeviceEntityMask, DeviceType } from './types';
import { deviceValueItemValidation } from './validators';
import type { Device, DeviceValue } from './types';
import type { FC } from 'react';
import type { Action, State } from '@table-library/react-table-library/types/common';
import { dialogStyle } from 'CustomTheme';
import { ButtonRow, SectionContent, MessageBox, useLayoutTitle } from 'components';
import { useRequest } from 'alova';
import { ButtonRow, MessageBox, SectionContent, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api';
import DeviceIcon from './DeviceIcon';
import DashboardDevicesDialog from './DevicesDialog';
import { formatValue } from './deviceValue';
import { DeviceEntityMask, DeviceType, DeviceValueUOM_s } from './types';
import type { Device, DeviceValue } from './types';
import { deviceValueItemValidation } from './validators';
const Devices: FC = () => {
const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext);
@@ -76,16 +74,19 @@ const Devices: FC = () => {
}
});
const { data: deviceData, send: readDeviceData } = useRequest((id) => EMSESP.readDeviceData(id), {
const { data: deviceData, send: readDeviceData } = useRequest((id: number) => EMSESP.readDeviceData(id), {
initialData: {
data: []
},
immediate: false
});
const { loading: submitting, send: writeDeviceValue } = useRequest((data) => EMSESP.writeDeviceValue(data), {
immediate: false
});
const { loading: submitting, send: writeDeviceValue } = useRequest(
(data: { id: number; c: string; v: unknown }) => EMSESP.writeDeviceValue(data),
{
immediate: false
}
);
useLayoutEffect(() => {
function updateSize() {
@@ -213,7 +214,7 @@ const Devices: FC = () => {
}
]);
const getSortIcon = (state: any, sortKey: any) => {
const getSortIcon = (state: State, sortKey: unknown) => {
if (state.sortKey === sortKey && state.reverse) {
return <KeyboardArrowDownOutlinedIcon />;
}
@@ -235,13 +236,14 @@ const Devices: FC = () => {
sortToggleType: SortToggleType.AlternateWithReset,
sortFns: {
NAME: (array) => array.sort((a, b) => a.id.toString().slice(2).localeCompare(b.id.toString().slice(2))),
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
VALUE: (array) => array.sort((a, b) => a.v.toString().localeCompare(b.v.toString()))
}
}
);
async function onSelectChange(action: any, state: any) {
setSelectedDevice(state.id);
async function onSelectChange(action: Action, state: State) {
setSelectedDevice(state.id as number);
if (action.type === 'ADD_BY_ID_EXCLUSIVELY') {
await readDeviceData(state.id);
}
@@ -259,8 +261,8 @@ const Devices: FC = () => {
};
const escFunction = useCallback(
(event: any) => {
if (event.keyCode === 27) {
(event: KeyboardEvent) => {
if (event.key === 'Escape') {
if (device_select) {
device_select.fns.onRemoveAll();
}
@@ -290,7 +292,7 @@ const Devices: FC = () => {
}
};
const escapeCsvCell = (cell: any) => {
const escapeCsvCell = (cell: string) => {
if (cell == null) {
return '';
}
@@ -336,9 +338,13 @@ const Devices: FC = () => {
: deviceData.data;
const csvData = data.reduce(
(csvString: any, rowItem: any) =>
csvString + columns.map(({ accessor }: any) => escapeCsvCell(accessor(rowItem))).join(';') + '\r\n',
columns.map(({ name }: any) => escapeCsvCell(name)).join(';') + '\r\n'
(csvString: string, rowItem: DeviceValue) =>
csvString +
columns
.map(({ accessor }: { accessor: (dv: DeviceValue) => unknown }) => escapeCsvCell(accessor(rowItem) as string))
.join(';') +
'\r\n',
columns.map(({ name }: { name: string }) => escapeCsvCell(name)).join(';') + '\r\n'
);
const csvFile = new Blob([csvData], { type: 'text/csv;charset:utf-8' });
@@ -363,7 +369,7 @@ const Devices: FC = () => {
.then(() => {
toast.success(LL.WRITE_CMD_SENT());
})
.catch((error) => {
.catch((error: Error) => {
toast.error(error.message);
})
.finally(async () => {
@@ -428,7 +434,7 @@ const Devices: FC = () => {
{coreData.connected && (
<Table data={{ nodes: coreData.devices }} select={device_select} theme={device_theme} layout={{ custom: true }}>
{(tableList: any) => (
{(tableList: Device[]) => (
<>
<Header>
<HeaderRow>
@@ -556,7 +562,7 @@ const Devices: FC = () => {
sort={dv_sort}
layout={{ custom: true, fixedHeader: true }}
>
{(tableList: any) => (
{(tableList: DeviceValue[]) => (
<>
<Header>
<HeaderRow>

View File

@@ -1,36 +1,35 @@
import { useEffect, useState } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning';
import {
Box,
Button,
CircularProgress,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
DialogContent,
DialogTitle,
FormHelperText,
Grid,
InputAdornment,
MenuItem,
TextField,
FormHelperText,
Grid,
Box,
Typography,
CircularProgress
Typography
} from '@mui/material';
import { useState, useEffect } from 'react';
import { dialogStyle } from 'CustomTheme';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
import { ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
import type { DeviceValue } from './types';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
import { dialogStyle } from 'CustomTheme';
import { ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { updateValue, numberValue } from 'utils';
import { validate } from 'validators';
type DashboardDevicesDialogProps = {
interface DashboardDevicesDialogProps {
open: boolean;
onClose: () => void;
onSave: (as: DeviceValue) => void;
@@ -38,7 +37,7 @@ type DashboardDevicesDialogProps = {
writeable: boolean;
validator: Schema;
progress: boolean;
};
}
const DevicesDialog = ({
open,
@@ -71,12 +70,12 @@ const DevicesDialog = ({
setFieldErrors(undefined);
await validate(validator, editItem);
onSave(editItem);
} catch (errors: any) {
setFieldErrors(errors);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
}
};
const setUom = (uom: number) => {
const setUom = (uom: DeviceValueUOM) => {
switch (uom) {
case DeviceValueUOM.HOURS:
return LL.HOURS();
@@ -133,7 +132,7 @@ const DevicesDialog = ({
fieldErrors={fieldErrors}
name="v"
label={LL.VALUE(1)}
value={numberValue(Math.round(editItem.v * 10) / 10)}
value={numberValue(Math.round((editItem.v as number) * 10) / 10)}
autoFocus
disabled={!writeable}
type="number"

View File

@@ -1,12 +1,13 @@
import { ToggleButton, ToggleButtonGroup } from '@mui/material';
import OptionIcon from './OptionIcon';
import { DeviceEntityMask } from './types';
import type { DeviceEntity } from './types';
type EntityMaskToggleProps = {
interface EntityMaskToggleProps {
onUpdate: (de: DeviceEntity) => void;
de: DeviceEntity;
};
}
const EntityMaskToggle = ({ onUpdate, de }: EntityMaskToggleProps) => {
const getMaskNumber = (newMask: string[]) => {
@@ -42,7 +43,7 @@ const EntityMaskToggle = ({ onUpdate, de }: EntityMaskToggleProps) => {
size="small"
color="secondary"
value={getMaskString(de.m)}
onChange={(event, mask) => {
onChange={(event, mask: string[]) => {
de.m = getMaskNumber(mask);
if (de.n === '' && de.m & DeviceEntityMask.DV_READONLY) {
de.m = de.m | DeviceEntityMask.DV_WEB_EXCLUDE;

View File

@@ -1,31 +1,35 @@
import type { FC } from 'react';
import { toast } from 'react-toastify';
import CommentIcon from '@mui/icons-material/CommentTwoTone';
import DownloadIcon from '@mui/icons-material/GetApp';
import GitHubIcon from '@mui/icons-material/GitHub';
import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone';
import {
Avatar,
Box,
Button,
Link,
List,
ListItem,
ListItemAvatar,
ListItemText,
Link,
Typography,
Button,
ListItemButton,
Avatar
ListItemText,
Typography
} from '@mui/material';
import * as EMSESP from 'project/api';
import { useRequest } from 'alova';
import { toast } from 'react-toastify';
import type { FC } from 'react';
import { SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from 'project/api';
import type { APIcall } from './types';
const Help: FC = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.HELP_OF(''));
const { send: getAPI, onSuccess: onGetAPI } = useRequest((data) => EMSESP.API(data), {
const { send: getAPI, onSuccess: onGetAPI } = useRequest((data: APIcall) => EMSESP.API(data), {
immediate: false
});
@@ -36,6 +40,7 @@ const Help: FC = () => {
type: 'text/plain'
})
);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
anchor.download = 'emsesp_' + event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt';
anchor.click();
URL.revokeObjectURL(anchor.href);
@@ -43,7 +48,7 @@ const Help: FC = () => {
});
const callAPI = async (device: string, entity: string) => {
await getAPI({ device, entity, id: 0 }).catch((error) => {
await getAPI({ device, entity, id: 0 }).catch((error: Error) => {
toast.error(error.message);
});
};

View File

@@ -1,18 +1,16 @@
import type { FC } from 'react';
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined';
import StarIcon from '@mui/icons-material/Star';
import StarOutlineIcon from '@mui/icons-material/StarOutline';
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';
import type { SvgIconProps } from '@mui/material';
import type { FC } from 'react';
type OptionType = 'deleted' | 'readonly' | 'web_exclude' | 'api_mqtt_exclude' | 'favorite';

View File

@@ -1,27 +1,26 @@
import { useCallback, useEffect, useState } from 'react';
import type { FC } from 'react';
import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify';
import AddIcon from '@mui/icons-material/Add';
import CancelIcon from '@mui/icons-material/Cancel';
import CircleIcon from '@mui/icons-material/Circle';
import WarningIcon from '@mui/icons-material/Warning';
import { Box, Button, Divider, Stack, Typography } 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 { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme';
// eslint-disable-next-line import/named
import { updateState, useRequest } from 'alova';
import { useState, useEffect, useCallback } from 'react';
import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify';
import SettingsSchedulerDialog from './SchedulerDialog';
import * as EMSESP from './api';
import { ScheduleFlag } from './types';
import { schedulerItemValidation } from './validators';
import type { ScheduleItem } from './types';
import type { FC } from 'react';
import { ButtonRow, FormLoader, SectionContent, BlockNavigation, useLayoutTitle } from 'components';
import { BlockNavigation, ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api';
import SettingsSchedulerDialog from './SchedulerDialog';
import { ScheduleFlag } from './types';
import type { Schedule, ScheduleItem } from './types';
import { schedulerItemValidation } from './validators';
const Scheduler: FC = () => {
const { LL, locale } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0);
@@ -40,7 +39,9 @@ const Scheduler: FC = () => {
force: true
});
const { send: writeSchedule } = useRequest((data) => EMSESP.writeSchedule(data), { immediate: false });
const { send: writeSchedule } = useRequest((data: Schedule) => EMSESP.writeSchedule(data), {
immediate: false
});
function hasScheduleChanged(si: ScheduleItem) {
return (
@@ -126,8 +127,8 @@ const Scheduler: FC = () => {
.then(() => {
toast.success(LL.SCHEDULE_UPDATED());
})
.catch((err) => {
toast.error(err.message);
.catch((error: Error) => {
toast.error(error.message);
})
.finally(async () => {
await fetchSchedule();
@@ -154,11 +155,13 @@ const Scheduler: FC = () => {
const onDialogSave = (updatedItem: ScheduleItem) => {
setDialogOpen(false);
updateState('schedule', (data) => {
updateState('schedule', (data: ScheduleItem[]) => {
const new_data = creating
? [...data.filter((si) => creating || si.o_id !== updatedItem.o_id), updatedItem]
: data.map((si) => (si.id === updatedItem.id ? { ...si, ...updatedItem } : si));
setNumChanges(new_data.filter((si) => hasScheduleChanged(si)).length);
return new_data;
});
};
@@ -202,7 +205,7 @@ const Scheduler: FC = () => {
theme={schedule_theme}
layout={{ custom: true }}
>
{(tableList: any) => (
{(tableList: ScheduleItem[]) => (
<>
<Header>
<HeaderRow>

View File

@@ -1,8 +1,9 @@
import { useEffect, useState } from 'react';
import AddIcon from '@mui/icons-material/Add';
import CancelIcon from '@mui/icons-material/Cancel';
import DoneIcon from '@mui/icons-material/Done';
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
import {
Box,
Button,
@@ -17,22 +18,19 @@ import {
ToggleButtonGroup,
Typography
} from '@mui/material';
import { useEffect, useState } from 'react';
import { ScheduleFlag } from './types';
import type { ScheduleItem } from './types';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
import { dialogStyle } from 'CustomTheme';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
import { BlockFormControlLabel, ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { updateValue } from 'utils';
import { validate } from 'validators';
type SchedulerDialogProps = {
import { ScheduleFlag } from './types';
import type { ScheduleItem } from './types';
interface SchedulerDialogProps {
open: boolean;
creating: boolean;
onClose: () => void;
@@ -40,7 +38,7 @@ type SchedulerDialogProps = {
selectedItem: ScheduleItem;
validator: Schema;
dow: string[];
};
}
const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, validator, dow }: SchedulerDialogProps) => {
const { LL } = useI18nContext();
@@ -65,8 +63,8 @@ const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, valida
setFieldErrors(undefined);
await validate(validator, editItem);
onSave(editItem);
} catch (errors: any) {
setFieldErrors(errors);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
}
};
@@ -132,7 +130,7 @@ const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, valida
size="small"
color="secondary"
value={getFlagString(editItem.flags)}
onChange={(event, flag) => {
onChange={(_event, flag: string[]) => {
setEditItem({ ...editItem, flags: getFlagNumber(flag) & 127 });
}}
>

View File

@@ -1,30 +1,30 @@
import { useContext, useEffect, useState } from 'react';
import type { FC } from 'react';
import { toast } from 'react-toastify';
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
import RefreshIcon from '@mui/icons-material/Refresh';
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
import { Button, Typography, Box } from '@mui/material';
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
import { Box, Button, Typography } from '@mui/material';
import { SortToggleType, useSort } from '@table-library/react-table-library/sort';
import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme';
import type { State } from '@table-library/react-table-library/types/common';
import { useRequest } from 'alova';
import { useState, useEffect, useContext } from 'react';
import { toast } from 'react-toastify';
import DashboardSensorsAnalogDialog from './SensorsAnalogDialog';
import DashboardSensorsTemperatureDialog from './SensorsTemperatureDialog';
import * as EMSESP from './api';
import { DeviceValueUOM, DeviceValueUOM_s, AnalogTypeNames, AnalogType } from './types';
import { temperatureSensorItemValidation, analogSensorItemValidation } from './validators';
import type { TemperatureSensor, AnalogSensor } from './types';
import type { FC } from 'react';
import { ButtonRow, SectionContent, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api';
import DashboardSensorsAnalogDialog from './SensorsAnalogDialog';
import DashboardSensorsTemperatureDialog from './SensorsTemperatureDialog';
import { AnalogType, AnalogTypeNames, DeviceValueUOM, DeviceValueUOM_s } from './types';
import type { AnalogSensor, TemperatureSensor, WriteAnalogSensor, WriteTemperatureSensor } from './types';
import { analogSensorItemValidation, temperatureSensorItemValidation } from './validators';
const Sensors: FC = () => {
const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext);
@@ -44,11 +44,14 @@ const Sensors: FC = () => {
}
});
const { send: writeTemperatureSensor } = useRequest((data) => EMSESP.writeTemperatureSensor(data), {
immediate: false
});
const { send: writeTemperatureSensor } = useRequest(
(data: WriteTemperatureSensor) => EMSESP.writeTemperatureSensor(data),
{
immediate: false
}
);
const { send: writeAnalogSensor } = useRequest((data) => EMSESP.writeAnalogSensor(data), {
const { send: writeAnalogSensor } = useRequest((data: WriteAnalogSensor) => EMSESP.writeAnalogSensor(data), {
immediate: false
});
@@ -116,7 +119,7 @@ const Sensors: FC = () => {
}
]);
const getSortIcon = (state: any, sortKey: any) => {
const getSortIcon = (state: State, sortKey: unknown) => {
if (state.sortKey === sortKey && state.reverse) {
return <KeyboardArrowDownOutlinedIcon />;
}
@@ -138,6 +141,7 @@ const Sensors: FC = () => {
sortToggleType: SortToggleType.AlternateWithReset,
sortFns: {
GPIO: (array) => array.sort((a, b) => a.g - b.g),
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
NAME: (array) => array.sort((a, b) => a.n.localeCompare(b.n)),
TYPE: (array) => array.sort((a, b) => a.t - b.t),
VALUE: (array) => array.sort((a, b) => a.v - b.v)
@@ -156,6 +160,7 @@ const Sensors: FC = () => {
},
sortToggleType: SortToggleType.AlternateWithReset,
sortFns: {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
NAME: (array) => array.sort((a, b) => a.n.localeCompare(b.n)),
VALUE: (array) => array.sort((a, b) => a.t - b.t)
}
@@ -189,10 +194,13 @@ const Sensors: FC = () => {
return formatted;
};
function formatValue(value: any, uom: number) {
function formatValue(value: unknown, uom: DeviceValueUOM) {
if (value === undefined) {
return '';
}
if (typeof value !== 'number') {
return value as string;
}
switch (uom) {
case DeviceValueUOM.HOURS:
return value ? formatDurationMin(value * 60) : LL.NUM_HOURS({ num: 0 });
@@ -201,10 +209,7 @@ const Sensors: FC = () => {
case DeviceValueUOM.SECONDS:
return LL.NUM_SECONDS({ num: value });
case DeviceValueUOM.NONE:
if (typeof value === 'number') {
return new Intl.NumberFormat().format(value);
}
return value;
return new Intl.NumberFormat().format(value);
case DeviceValueUOM.DEGREES:
case DeviceValueUOM.DEGREES_R:
case DeviceValueUOM.FAHRENHEIT:
@@ -300,7 +305,7 @@ const Sensors: FC = () => {
const RenderTemperatureSensors = () => (
<Table data={{ nodes: sensorData.ts }} theme={temperature_theme} sort={temperature_sort} layout={{ custom: true }}>
{(tableList: any) => (
{(tableList: TemperatureSensor[]) => (
<>
<Header>
<HeaderRow>
@@ -341,7 +346,7 @@ const Sensors: FC = () => {
const RenderAnalogSensors = () => (
<Table data={{ nodes: sensorData.as }} theme={analog_theme} sort={analog_sort} layout={{ custom: true }}>
{(tableList: any) => (
{(tableList: AnalogSensor[]) => (
<>
<Header>
<HeaderRow>

View File

@@ -1,42 +1,41 @@
import { useEffect, useState } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
import WarningIcon from '@mui/icons-material/Warning';
import {
Button,
Typography,
Box,
Button,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
InputAdornment,
DialogContent,
DialogTitle,
Grid,
InputAdornment,
MenuItem,
TextField
TextField,
Typography
} from '@mui/material';
import { useState, useEffect } from 'react';
import { dialogStyle } from 'CustomTheme';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
import { ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
import { AnalogType, AnalogTypeNames, DeviceValueUOM_s } from './types';
import type { AnalogSensor } from './types';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
import { dialogStyle } from 'CustomTheme';
import { ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
type DashboardSensorsAnalogDialogProps = {
interface DashboardSensorsAnalogDialogProps {
open: boolean;
onClose: () => void;
onSave: (as: AnalogSensor) => void;
creating: boolean;
selectedItem: AnalogSensor;
validator: Schema;
};
}
const SensorsAnalogDialog = ({
open,
@@ -67,8 +66,8 @@ const SensorsAnalogDialog = ({
setFieldErrors(undefined);
await validate(validator, editItem);
onSave(editItem);
} catch (errors: any) {
setFieldErrors(errors);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
}
};

View File

@@ -1,38 +1,37 @@
import { useEffect, useState } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning';
import {
Button,
Typography,
Box,
Button,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
InputAdornment,
DialogContent,
DialogTitle,
Grid,
TextField
InputAdornment,
TextField,
Typography
} from '@mui/material';
import { useState, useEffect } from 'react';
import type { TemperatureSensor } from './types';
import { dialogStyle } from 'CustomTheme';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
import { dialogStyle } from 'CustomTheme';
import { ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
type SensorsTemperatureDialogProps = {
import type { TemperatureSensor } from './types';
interface SensorsTemperatureDialogProps {
open: boolean;
onClose: () => void;
onSave: (ts: TemperatureSensor) => void;
selectedItem: TemperatureSensor;
validator: Schema;
};
}
const SensorsTemperatureDialog = ({
open,
@@ -62,8 +61,8 @@ const SensorsTemperatureDialog = ({
setFieldErrors(undefined);
await validate(validator, editItem);
onSave(editItem);
} catch (errors: any) {
setFieldErrors(errors);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
}
};

View File

@@ -1,18 +1,19 @@
import { useEffect } from 'react';
import type { FC } from 'react';
import RefreshIcon from '@mui/icons-material/Refresh';
import { Button } from '@mui/material';
import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
import { useTheme as tableTheme } from '@table-library/react-table-library/theme';
import { useRequest } from 'alova';
import { useEffect } from 'react';
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { Translation } from 'i18n/i18n-types';
import * as EMSESP from './api';
import type { Stat } from './types';
import type { Translation } from 'i18n/i18n-types';
import type { FC } from 'react';
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
const SystemActivity: FC = () => {
const { data: data, send: loadData, error } = useRequest(EMSESP.readActivity);
@@ -65,7 +66,8 @@ const SystemActivity: FC = () => {
};
});
const showName = (id: any) => {
const showName = (id: number) => {
// TODO fix this
const name: keyof Translation['STATUS_NAMES'] = id;
return LL.STATUS_NAMES[name]();
};
@@ -92,7 +94,7 @@ const SystemActivity: FC = () => {
return (
<>
<Table data={{ nodes: data.stats }} theme={stats_theme} layout={{ custom: true }}>
{(tableList: any) => (
{(tableList: Stat[]) => (
<>
<Header>
<HeaderRow>

View File

@@ -1,19 +1,21 @@
import { alovaInstance } from 'api/endpoints';
import type {
APIcall,
Settings,
Activity,
CoreData,
Devices,
DeviceEntity,
WriteTemperatureSensor,
WriteAnalogSensor,
SensorData,
Entities,
DeviceData,
DeviceEntity,
Devices,
Entities,
EntityItem,
Schedule,
ScheduleItem,
EntityItem
SensorData,
Settings,
WriteAnalogSensor,
WriteTemperatureSensor
} from './types';
import { alovaInstance } from 'api/endpoints';
// DashboardDevices
export const readCoreData = () => alovaInstance.Get<CoreData>(`/rest/coreData`);
@@ -23,11 +25,12 @@ export const readDeviceData = (id: number) =>
params: { id }, // TODO replace later with id
responseType: 'arraybuffer' // uses msgpack
});
export const writeDeviceValue = (data: any) => alovaInstance.Post('/rest/writeDeviceValue', data);
export const writeDeviceValue = (data: { id: number; c: string; v: unknown }) =>
alovaInstance.Post('/rest/writeDeviceValue', data);
// Application Settings
export const readSettings = () => alovaInstance.Get<Settings>('/rest/settings');
export const writeSettings = (data: any) => alovaInstance.Post('/rest/settings', data);
export const writeSettings = (data: Settings) => alovaInstance.Post('/rest/settings', data);
export const getBoardProfile = (boardProfile: string) =>
alovaInstance.Get('/rest/boardProfile', {
params: { boardProfile }
@@ -59,20 +62,27 @@ export const readDeviceEntities = (id: number) =>
alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, {
params: { id }, // TODO replace later with 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 }));
transformData(data) {
return (data as DeviceEntity[]).map((de: DeviceEntity) => ({
...de,
o_m: de.m,
o_cn: de.cn,
o_mi: de.mi,
o_ma: de.ma
}));
}
});
export const readDevices = () => alovaInstance.Get<Devices>('/rest/devices');
export const resetCustomizations = () => alovaInstance.Post('/rest/resetCustomizations');
export const writeCustomizationEntities = (data: any) => alovaInstance.Post('/rest/customizationEntities', data);
export const writeCustomizationEntities = (data: { id: number; entity_ids: string[] }) =>
alovaInstance.Post('/rest/customizationEntities', data);
// SettingsScheduler
export const readSchedule = () =>
alovaInstance.Get<ScheduleItem[]>('/rest/schedule', {
name: 'schedule',
transformData(data: any) {
return data.schedule.map((si: ScheduleItem) => ({
transformData(data) {
return (data as Schedule).schedule.map((si: ScheduleItem) => ({
...si,
o_id: si.id,
o_active: si.active,
@@ -85,14 +95,14 @@ export const readSchedule = () =>
}));
}
});
export const writeSchedule = (data: any) => alovaInstance.Post('/rest/schedule', data);
export const writeSchedule = (data: Schedule) => alovaInstance.Post('/rest/schedule', data);
// SettingsEntities
export const readCustomEntities = () =>
alovaInstance.Get<EntityItem[]>('/rest/customEntities', {
name: 'entities',
transformData(data: any) {
return data.entities.map((ei: EntityItem) => ({
transformData(data) {
return (data as Entities).entities.map((ei: EntityItem) => ({
...ei,
o_id: ei.id,
o_device_id: ei.device_id,
@@ -107,4 +117,5 @@ export const readCustomEntities = () =>
}));
}
});
export const writeCustomEntities = (data: any) => alovaInstance.Post('/rest/customEntities', data);
export const writeCustomEntities = (data: { id: number; entity_ids: string[] }) =>
alovaInstance.Post('/rest/customEntities', data);

View File

@@ -1,6 +1,7 @@
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
import type { TranslationFunctions } from 'i18n/i18n-types';
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
const formatDurationMin = (LL: TranslationFunctions, duration_min: number) => {
const days = Math.trunc((duration_min * 60000) / 86400000);
const hours = Math.trunc((duration_min * 60000) / 3600000) % 24;
@@ -24,8 +25,8 @@ const formatDurationMin = (LL: TranslationFunctions, duration_min: number) => {
return formatted;
};
export function formatValue(LL: TranslationFunctions, value: any, uom: number) {
if (value === undefined) {
export function formatValue(LL: TranslationFunctions, value: unknown, uom: DeviceValueUOM) {
if (typeof value !== 'number') {
return '';
}
switch (uom) {

View File

@@ -119,7 +119,7 @@ export interface Devices {
export interface DeviceValue {
id: string; // index, contains mask+name
v: any; // value, Number or String
v: unknown; // value, Number or String
u: number; // uom
c?: string; // command, optional
l?: string[]; // list, optional
@@ -134,10 +134,10 @@ export interface DeviceData {
export interface DeviceEntity {
id: string; // shortname
v?: any; // value, in any format, optional
v?: unknown; // value, in any format, optional
n?: string; // fullname, optional
cn?: string; // custom fullname, optional
m: number; // mask
m: DeviceEntityMask; // mask
w: boolean; // writeable
mi?: number; // min value
ma?: number; // max value
@@ -230,9 +230,7 @@ export const AnalogTypeNames = [
'PWM 2'
];
type BoardProfiles = {
[name: string]: string;
};
type BoardProfiles = Record<string, string>;
export const BOARD_PROFILES: BoardProfiles = {
S32: 'BBQKees Gateway S32',
@@ -265,7 +263,7 @@ export interface BoardProfile {
export interface APIcall {
device: string;
entity: string;
id: any;
id: unknown;
}
export interface WriteAnalogSensor {
id: number;
@@ -306,6 +304,10 @@ export interface ScheduleItem {
o_name?: string;
}
export interface Schedule {
schedule: ScheduleItem[];
}
export enum ScheduleFlag {
SCHEDULE_SUN = 1,
SCHEDULE_MON = 2,
@@ -327,7 +329,7 @@ export interface EntityItem {
factor: number;
uom: number;
value_type: number;
value?: any;
value?: unknown;
writeable: boolean;
deleted?: boolean;
o_id?: number;
@@ -341,7 +343,7 @@ export interface EntityItem {
o_value_type?: number;
o_deleted?: boolean;
o_writeable?: boolean;
o_value?: any;
o_value?: unknown;
}
export interface Entities {

View File

@@ -1,8 +1,9 @@
import Schema from 'async-validator';
import type { AnalogSensor, DeviceValue, ScheduleItem, Settings } from './types';
import type { InternalRuleItem } from 'async-validator';
import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared';
import type { AnalogSensor, DeviceValue, ScheduleItem, Settings } from './types';
export const GPIO_VALIDATOR = {
validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) {
if (
@@ -239,7 +240,7 @@ export const deviceValueItemValidation = (dv: DeviceValue) =>
v: [
{ required: true, message: 'Value is required' },
{
validator(rule: InternalRuleItem, value: any, callback: (error?: string) => void) {
validator(rule: InternalRuleItem, value: unknown, callback: (error?: string) => void) {
if (typeof value === 'number' && dv.m && dv.x && (value < dv.m || value > dv.x)) {
callback('Value out of range');
}