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

1
.gitignore vendored
View File

@@ -63,3 +63,4 @@ bw-output/
# standalone executable for testing
emsesp
interface/tsconfig.tsbuildinfo

View File

@@ -1,8 +1,13 @@
{
"plugins": ["@trivago/prettier-plugin-sort-imports"],
"trailingComma": "none",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"printWidth": 120,
"bracketSpacing": true
"bracketSpacing": true,
"importOrder": ["^react", "^@mui/(.*)$", "^api*/(.*)$", "<THIRD_PARTY_MODULES>", "^[./]"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"importOrderGroupNamespaceSpecifiers": true
}

View File

@@ -6,6 +6,10 @@
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
},
"eslint.validate": [
"typescript"
],
"eslint.codeActionsOnSave.rules": null,
"eslint.nodePath": "interface/.yarn/sdks",
"eslint.workingDirectories": ["interface"],
"prettier.prettierPath": "",
@@ -87,5 +91,6 @@
"cSpell.enableFiletypes": [
"!cpp",
"!typescript"
]
],
"typescript.preferences.preferTypeOnlyAutoImports": true
}

View File

@@ -1,12 +0,0 @@
node_modules/
build/
dist/
.yarn/
.prettierrc
.eslintrc*
env.d.ts
progmem-generator.js
unpack.ts
vite.config.ts
package.json

View File

@@ -1,108 +0,0 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": [
"eslint:recommended",
// "airbnb/hooks",
// "airbnb-typescript",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:prettier/recommended",
"plugin:import/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module",
"tsconfigRootDir": ".",
"project": ["tsconfig.json"]
},
"plugins": ["react", "@typescript-eslint", "autofix", "react-hooks"],
"settings": {
"import/resolver": {
"typescript": {
"project": "./tsconfig.json"
}
},
"react": {
"version": "18.x"
}
},
"rules": {
"object-shorthand": "error",
"no-console": "warn",
"@typescript-eslint/consistent-type-definitions": ["off", "type"],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-enum-comparison": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/restrict-plus-operands": "off",
"@typescript-eslint/no-unused-expressions": "off",
"@typescript-eslint/no-implied-eval": "off",
"@typescript-eslint/no-misused-promises": "off",
"arrow-body-style": ["error", "as-needed"],
"react-hooks/exhaustive-deps": "warn",
"@typescript-eslint/consistent-type-imports": [
"error",
{
"prefer": "type-imports"
}
],
"import/order": [
"warn",
{
"groups": ["builtin", "external", "parent", "sibling", "index", "object", "type"],
"pathGroups": [
{
"pattern": "@/**/**",
"group": "parent",
"position": "before"
}
],
"alphabetize": { "order": "asc" }
}
],
// "autofix/no-unused-vars": [
// "error",
// {
// "argsIgnorePattern": "^_",
// "ignoreRestSiblings": true,
// "destructuredArrayIgnorePattern": "^_"
// }
// ],
"react/self-closing-comp": [
"error",
{
"component": true,
"html": true
}
],
"@typescript-eslint/ban-types": [
"error",
{
"extendDefaults": true,
"types": {
"{}": false
}
}
],
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
}
]
}
}

View File

@@ -0,0 +1,33 @@
// @ts-check
import eslint from '@eslint/js';
import prettierConfig from 'eslint-config-prettier';
import tseslint from 'typescript-eslint';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
prettierConfig,
{
languageOptions: {
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname
}
}
},
{
ignores: ['dist/*', '*.js', '**/*.cjs', '**/unpack.ts']
},
{
rules: {
'@typescript-eslint/no-unsafe-enum-comparison': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-misused-promises': [
'error',
{
checksVoidReturn: false
}
]
}
}
);

View File

@@ -19,25 +19,24 @@
"typesafe-i18n": "typesafe-i18n --no-watch",
"webUI": "node progmem-generator.js",
"format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'",
"lint": "eslint . --cache --fix"
"lint": "eslint . --fix"
},
"dependencies": {
"@alova/adapter-xhr": "^1.0.3",
"@alova/scene-react": "^1.5.0",
"@babel/core": "^7.24.4",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.15.15",
"@mui/material": "^5.15.15",
"@table-library/react-table-library": "4.1.7",
"@types/imagemin": "^8.0.5",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.12.7",
"@types/react": "^18.2.78",
"@types/react": "^18.2.79",
"@types/react-dom": "^18.2.25",
"@types/react-router-dom": "^5.3.3",
"alova": "^2.19.1",
"alova": "^2.20.0",
"async-validator": "^4.2.5",
"eslint-plugin-prettier": "^5.1.3",
"history": "^5.3.0",
"jwt-decode": "^4.0.0",
"lodash-es": "^4.17.21",
@@ -52,23 +51,19 @@
"typescript": "^5.4.5"
},
"devDependencies": {
"@eslint/js": "^9.1.1",
"@preact/compat": "^17.1.2",
"@preact/preset-vite": "^2.8.2",
"@typescript-eslint/eslint-plugin": "^7.7.0",
"@typescript-eslint/parser": "^7.7.0",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"concurrently": "^8.2.2",
"eslint": "8.57.0",
"eslint": "^9.1.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-autofix": "^1.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"preact": "^10.20.2",
"prettier": "^3.2.5",
"rollup-plugin-visualizer": "^5.12.0",
"terser": "^5.30.3",
"vite": "^5.2.8",
"typescript-eslint": "^7.7.0",
"vite": "^5.2.10",
"vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^4.3.2"
},

View File

@@ -1,8 +1,8 @@
import { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream } from 'fs';
import { resolve, relative, sep } from 'path';
import zlib from 'zlib';
import mime from 'mime-types';
import crypto from 'crypto';
import { createWriteStream, existsSync, readFileSync, readdirSync, unlinkSync } from 'fs';
import mime from 'mime-types';
import { relative, resolve, sep } from 'path';
import zlib from 'zlib';
const ARDUINO_INCLUDES = '#include <Arduino.h>\n\n';
const INDENT = ' ';

View File

@@ -1,16 +1,14 @@
import { useEffect, useState } from 'react';
import { ToastContainer, Slide } from 'react-toastify';
import type { FC } from 'react';
import { Slide, ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.min.css';
import { localStorageDetector } from 'typesafe-i18n/detectors';
import type { FC } from 'react';
import AppRouting from 'AppRouting';
import CustomTheme from 'CustomTheme';
import TypesafeI18n from 'i18n/i18n-react';
import { detectLocale } from 'i18n/i18n-util';
import { loadLocaleAsync } from 'i18n/i18n-util.async';
import { localStorageDetector } from 'typesafe-i18n/detectors';
const detectedLocale = detectLocale(localStorageDetector);

View File

@@ -1,14 +1,11 @@
import { useContext, useEffect } from 'react';
import { Route, Routes, Navigate, useLocation } from 'react-router-dom';
import { toast } from 'react-toastify';
import type { FC } from 'react';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import { toast } from 'react-toastify';
import AuthenticatedRouting from 'AuthenticatedRouting';
import SignIn from 'SignIn';
import { RequireAuthenticated, RequireUnauthenticated } from 'components';
import { Authentication, AuthenticationContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';

View File

@@ -1,7 +1,6 @@
import { useContext, type FC } from 'react';
import { Navigate, Routes, Route } from 'react-router-dom';
import { type FC, useContext } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import Help from './project/Help';
import { Layout } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import Settings from 'framework/Settings';
@@ -21,6 +20,8 @@ import Devices from 'project/Devices';
import Scheduler from 'project/Scheduler';
import Sensors from 'project/Sensors';
import Help from './project/Help';
const AuthenticatedRouting: FC = () => {
const { me } = useContext(AuthenticatedContext);
return (

View File

@@ -1,7 +1,8 @@
import { CssBaseline } from '@mui/material';
import { createTheme, responsiveFontSizes, ThemeProvider } from '@mui/material/styles';
import type { FC } from 'react';
import { CssBaseline } from '@mui/material';
import { ThemeProvider, createTheme, responsiveFontSizes } from '@mui/material/styles';
import type { RequiredChildrenProps } from 'utils';
export const dialogStyle = {

View File

@@ -1,19 +1,17 @@
import ForwardIcon from '@mui/icons-material/Forward';
import { Box, Paper, Typography, MenuItem, TextField, Button } from '@mui/material';
import { useRequest } from 'alova';
import { useContext, useState } from 'react';
import { toast } from 'react-toastify';
import type { ValidateFieldsError } from 'async-validator';
import type { Locales } from 'i18n/i18n-types';
import type { ChangeEventHandler, FC } from 'react';
import type { SignInRequest } from 'types';
import { toast } from 'react-toastify';
import ForwardIcon from '@mui/icons-material/Forward';
import { Box, Button, MenuItem, Paper, TextField, Typography } from '@mui/material';
import * as AuthenticationApi from 'api/authentication';
import { PROJECT_NAME } from 'api/env';
import { useRequest } from 'alova';
import type { ValidateFieldsError } from 'async-validator';
import { ValidatedPasswordField, ValidatedTextField } from 'components';
import { AuthenticationContext } from 'contexts/authentication';
import DEflag from 'i18n/DE.svg';
import FRflag from 'i18n/FR.svg';
import GBflag from 'i18n/GB.svg';
@@ -25,7 +23,9 @@ import SKflag from 'i18n/SK.svg';
import SVflag from 'i18n/SV.svg';
import TRflag from 'i18n/TR.svg';
import { I18nContext } from 'i18n/i18n-react';
import type { Locales } from 'i18n/i18n-types';
import { loadLocaleAsync } from 'i18n/i18n-util.async';
import type { SignInRequest } from 'types';
import { onEnterCallback, updateValue } from 'utils';
import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators';
@@ -54,7 +54,7 @@ const SignIn: FC = () => {
const updateLoginRequestValue = updateValue(setSignInRequest);
const signIn = async () => {
await callSignIn(signInRequest).catch((event) => {
await callSignIn(signInRequest).catch((event: Error) => {
if (event.message === 'Unauthorized') {
toast.warning(LL.INVALID_LOGIN());
} else {
@@ -72,8 +72,8 @@ const SignIn: FC = () => {
try {
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
await signIn();
} catch (errors: any) {
setFieldErrors(errors);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setProcessing(false);
}
};

View File

@@ -1,7 +1,7 @@
import type { APSettingsType, APStatusType } from 'types';
import { alovaInstance } from './endpoints';
import type { APSettings, APStatus } from 'types';
export const readAPStatus = () => alovaInstance.Get<APStatus>('/rest/apStatus');
export const readAPSettings = () => alovaInstance.Get<APSettings>('/rest/apSettings');
export const updateAPSettings = (data: APSettings) => alovaInstance.Post<APSettings>('/rest/apSettings', data);
export const readAPStatus = () => alovaInstance.Get<APStatusType>('/rest/apStatus');
export const readAPSettings = () => alovaInstance.Get<APSettingsType>('/rest/apSettings');
export const updateAPSettings = (data: APSettingsType) => alovaInstance.Post<APSettingsType>('/rest/apSettings', data);

View File

@@ -1,10 +1,11 @@
import { jwtDecode } from 'jwt-decode';
import { ACCESS_TOKEN, alovaInstance } from './endpoints';
import type * as H from 'history';
import type { Path } from 'react-router-dom';
import type * as H from 'history';
import { jwtDecode } from 'jwt-decode';
import type { Me, SignInRequest, SignInResponse } from 'types';
import { ACCESS_TOKEN, alovaInstance } from './endpoints';
export const SIGN_IN_PATHNAME = 'loginPathname';
export const SIGN_IN_SEARCH = 'loginSearch';

View File

@@ -1,13 +1,11 @@
import { xhrRequestAdapter } from '@alova/adapter-xhr';
import { createAlova } from 'alova';
import ReactHook from 'alova/react';
import { unpack } from '../api/unpack';
export const ACCESS_TOKEN = 'access_token';
const host = window.location.host;
export const EVENT_SOURCE_ROOT = 'http://' + host + '/es/';
export const alovaInstance = createAlova({
statesHook: ReactHook,
timeout: 3000, // 3 seconds but throwing a timeout error
@@ -37,9 +35,9 @@ export const alovaInstance = createAlova({
} else if (response.status >= 400) {
throw new Error(response.statusText);
}
const data = await response.data;
const data: ArrayBuffer = (await response.data) as ArrayBuffer;
if (response.data instanceof ArrayBuffer) {
return unpack(data);
return unpack(data) as ArrayBuffer;
}
return data;
}

View File

@@ -1,6 +1,7 @@
import { alovaInstance } from './endpoints';
import type { MqttSettingsType, MqttStatusType } from 'types';
import { alovaInstance } from './endpoints';
export const readMqttStatus = () => alovaInstance.Get<MqttStatusType>('/rest/mqttStatus');
export const readMqttSettings = () => alovaInstance.Get<MqttSettingsType>('/rest/mqttSettings');
export const updateMqttSettings = (data: MqttSettingsType) =>

View File

@@ -1,8 +1,8 @@
import type { NetworkSettingsType, NetworkStatusType, WiFiNetworkList } from 'types';
import { alovaInstance } from './endpoints';
import type { WiFiNetworkList, NetworkSettings, NetworkStatus } from 'types';
export const readNetworkStatus = () => alovaInstance.Get<NetworkStatus>('/rest/networkStatus');
export const readNetworkStatus = () => alovaInstance.Get<NetworkStatusType>('/rest/networkStatus');
export const scanNetworks = () => alovaInstance.Get('/rest/scanNetworks');
export const listNetworks = () =>
alovaInstance.Get<WiFiNetworkList>('/rest/listNetworks', {
@@ -10,6 +10,6 @@ export const listNetworks = () =>
timeout: 20000 // timeout 20 seconds
});
export const readNetworkSettings = () =>
alovaInstance.Get<NetworkSettings>('/rest/networkSettings', { name: 'networkSettings' });
export const updateNetworkSettings = (wifiSettings: NetworkSettings) =>
alovaInstance.Post<NetworkSettings>('/rest/networkSettings', wifiSettings);
alovaInstance.Get<NetworkSettingsType>('/rest/networkSettings', { name: 'networkSettings' });
export const updateNetworkSettings = (wifiSettings: NetworkSettingsType) =>
alovaInstance.Post<NetworkSettingsType>('/rest/networkSettings', wifiSettings);

View File

@@ -1,11 +1,13 @@
import { alovaInstance } from './endpoints';
import type { NTPSettings, NTPStatus, Time } from 'types';
import type { NTPSettingsType, NTPStatusType, Time } from 'types';
export const readNTPStatus = () => alovaInstance.Get<NTPStatus>('/rest/ntpStatus');
import { alovaInstance } from './endpoints';
export const readNTPStatus = () => alovaInstance.Get<NTPStatusType>('/rest/ntpStatus');
export const readNTPSettings = () =>
alovaInstance.Get<NTPSettings>('/rest/ntpSettings', {
alovaInstance.Get<NTPSettingsType>('/rest/ntpSettings', {
name: 'ntpSettings'
});
export const updateNTPSettings = (data: NTPSettings) => alovaInstance.Post<NTPSettings>('/rest/ntpSettings', data);
export const updateNTPSettings = (data: NTPSettingsType) =>
alovaInstance.Post<NTPSettingsType>('/rest/ntpSettings', data);
export const updateTime = (data: Time) => alovaInstance.Post<Time>('/rest/time', data);

View File

@@ -1,10 +1,10 @@
import type { SecuritySettingsType, Token } from 'types';
import { alovaInstance } from './endpoints';
import type { SecuritySettings, Token } from 'types';
export const readSecuritySettings = () => alovaInstance.Get<SecuritySettingsType>('/rest/securitySettings');
export const readSecuritySettings = () => alovaInstance.Get<SecuritySettings>('/rest/securitySettings');
export const updateSecuritySettings = (securitySettings: SecuritySettings) =>
export const updateSecuritySettings = (securitySettings: SecuritySettingsType) =>
alovaInstance.Post('/rest/securitySettings', securitySettings);
export const generateToken = (username?: string) =>

View File

@@ -1,5 +1,11 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import type { ESPSystemStatus, LogSettings, OTASettings, SystemStatus } from 'types';
import { alovaInstance, alovaInstanceGH } from './endpoints';
import type { OTASettings, SystemStatus, LogSettings, ESPSystemStatus } from 'types';
// ESPSystemStatus - also used to ping in Restart monitor for pinging
export const readESPSystemStatus = () => alovaInstance.Get<ESPSystemStatus>('/rest/ESPSystemStatus');
@@ -14,23 +20,24 @@ export const factoryReset = () => alovaInstance.Post('/rest/factoryReset');
// OTA
export const readOTASettings = () => alovaInstance.Get<OTASettings>(`/rest/otaSettings`);
export const updateOTASettings = (data: any) => alovaInstance.Post('/rest/otaSettings', data);
export const updateOTASettings = (data: OTASettings) => alovaInstance.Post('/rest/otaSettings', data);
// SystemLog
export const readLogSettings = () => alovaInstance.Get<LogSettings>(`/rest/logSettings`);
export const updateLogSettings = (data: any) => alovaInstance.Post('/rest/logSettings', data);
export const updateLogSettings = (data: LogSettings) => alovaInstance.Post('/rest/logSettings', data);
export const fetchLog = () => alovaInstance.Post('/rest/fetchLog');
export const fetchLogES = () => alovaInstance.Get('/es/log');
// Get versions from github
export const getStableVersion = () =>
alovaInstanceGH.Get('latest', {
transformData(response: any) {
transformData(response) {
return response.data.name.substring(1);
}
});
export const getDevVersion = () =>
alovaInstanceGH.Get('tags/latest', {
transformData(response: any) {
transformData(response) {
return response.data.name.split(/\s+/).splice(-1)[0].substring(1);
}
});

View File

@@ -1,6 +1,7 @@
import type { FC } from 'react';
import { Box } from '@mui/material';
import type { BoxProps } from '@mui/material';
import type { FC } from 'react';
const ButtonRow: FC<BoxProps> = ({ children, ...rest }) => (
<Box

View File

@@ -1,10 +1,11 @@
import type { FC } from 'react';
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
import ErrorIcon from '@mui/icons-material/Error';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import ReportProblemOutlinedIcon from '@mui/icons-material/ReportProblemOutlined';
import { Box, Typography, useTheme } from '@mui/material';
import type { BoxProps, SvgIconProps, Theme } from '@mui/material';
import type { FC } from 'react';
type MessageBoxLevel = 'warning' | 'success' | 'info' | 'error';

View File

@@ -1,6 +1,7 @@
import { Paper, Divider } from '@mui/material';
import type { FC } from 'react';
import { Divider, Paper } from '@mui/material';
import type { RequiredChildrenProps } from 'utils';
interface SectionContentProps extends RequiredChildrenProps {

View File

@@ -1,6 +1,7 @@
import type { FC } from 'react';
import { FormControlLabel } from '@mui/material';
import type { FormControlLabelProps } from '@mui/material';
import type { FC } from 'react';
const BlockFormControlLabel: FC<FormControlLabelProps> = (props) => (
<div>

View File

@@ -1,11 +1,12 @@
import { useState } from 'react';
import type { FC } from 'react';
import VisibilityIcon from '@mui/icons-material/Visibility';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import { IconButton, InputAdornment } from '@mui/material';
import { useState } from 'react';
import ValidatedTextField from './ValidatedTextField';
import type { ValidatedTextFieldProps } from './ValidatedTextField';
import type { FC } from 'react';
type ValidatedPasswordFieldProps = Omit<ValidatedTextFieldProps, 'type'>;

View File

@@ -1,7 +1,9 @@
import type { FC } from 'react';
import { FormHelperText, TextField } from '@mui/material';
import type { TextFieldProps } from '@mui/material';
import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react';
interface ValidatedFieldProps {
fieldErrors?: ValidateFieldsError;

View File

@@ -1,13 +1,16 @@
import { Box, Toolbar } from '@mui/material';
import { useState, useEffect } from 'react';
import { useEffect, useState } from 'react';
import type { FC } from 'react';
import { useLocation } from 'react-router-dom';
import { Box, Toolbar } from '@mui/material';
import { PROJECT_NAME } from 'api/env';
import type { RequiredChildrenProps } from 'utils';
import LayoutAppBar from './LayoutAppBar';
import LayoutDrawer from './LayoutDrawer';
import { LayoutContext } from './context';
import type { FC } from 'react';
import type { RequiredChildrenProps } from 'utils';
import { PROJECT_NAME } from 'api/env';
export const DRAWER_WIDTH = 210;

View File

@@ -1,6 +1,7 @@
import type { FC } from 'react';
import MenuIcon from '@mui/icons-material/Menu';
import { AppBar, IconButton, Toolbar, Typography } from '@mui/material';
import type { FC } from 'react';
export const DRAWER_WIDTH = 210;

View File

@@ -1,12 +1,12 @@
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
import { DRAWER_WIDTH } from './Layout';
import LayoutMenu from './LayoutMenu';
import type { FC } from 'react';
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
import { PROJECT_NAME } from 'api/env';
import { DRAWER_WIDTH } from './Layout';
import LayoutMenu from './LayoutMenu';
const LayoutDrawerLogo = styled('img')(({ theme }) => ({
[theme.breakpoints.down('sm')]: {
height: 24,

View File

@@ -1,3 +1,6 @@
import { useContext, useState } from 'react';
import type { ChangeEventHandler, FC } from 'react';
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import AssessmentIcon from '@mui/icons-material/Assessment';
import CategoryIcon from '@mui/icons-material/Category';
@@ -9,28 +12,23 @@ import PersonIcon from '@mui/icons-material/Person';
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
import SensorsIcon from '@mui/icons-material/Sensors';
import SettingsIcon from '@mui/icons-material/Settings';
import {
Divider,
List,
Avatar,
Box,
Button,
Popover,
Avatar,
MenuItem,
TextField,
Divider,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText
ListItemText,
MenuItem,
Popover,
TextField
} from '@mui/material';
import { useContext, useState } from 'react';
import type { Locales } from 'i18n/i18n-types';
import type { FC, ChangeEventHandler } from 'react';
import LayoutMenuItem from 'components/layout/LayoutMenuItem';
import { AuthenticatedContext } from 'contexts/authentication';
import DEflag from 'i18n/DE.svg';
import FRflag from 'i18n/FR.svg';
import GBflag from 'i18n/GB.svg';
@@ -41,8 +39,8 @@ import PLflag from 'i18n/PL.svg';
import SKflag from 'i18n/SK.svg';
import SVflag from 'i18n/SV.svg';
import TRflag from 'i18n/TR.svg';
import { I18nContext } from 'i18n/i18n-react';
import type { Locales } from 'i18n/i18n-types';
import { loadLocaleAsync } from 'i18n/i18n-util.async';
const LayoutMenu: FC = () => {
@@ -63,7 +61,7 @@ const LayoutMenu: FC = () => {
setLocale(loc);
};
const handleClick = (event: any) => {
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};

View File

@@ -1,7 +1,8 @@
import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
import { Link, useLocation } from 'react-router-dom';
import type { SvgIconProps } from '@mui/material';
import type { FC } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
import type { SvgIconProps } from '@mui/material';
import { routeMatches } from 'utils';

View File

@@ -1,8 +1,9 @@
import type { FC } from 'react';
import { Link } from 'react-router-dom';
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
import { Avatar, ListItem, ListItemAvatar, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
import { Link } from 'react-router-dom';
import type { SvgIconProps } from '@mui/material';
import type { FC } from 'react';
interface ListMenuItemProps {
icon: React.ComponentType<SvgIconProps>;

View File

@@ -1,4 +1,4 @@
import { useRef, useEffect, createContext, useContext } from 'react';
import { createContext, useContext, useEffect, useRef } from 'react';
export interface LayoutContextValue {
title: string;

View File

@@ -1,6 +1,7 @@
import type { FC } from 'react';
import WarningIcon from '@mui/icons-material/Warning';
import { Box, Paper, Typography } from '@mui/material';
import type { FC } from 'react';
interface ApplicationErrorProps {
message?: string;

View File

@@ -1,9 +1,9 @@
import RefreshIcon from '@mui/icons-material/Refresh';
import { Box, Button, CircularProgress, Typography } from '@mui/material';
import type { FC } from 'react';
import { MessageBox } from 'components';
import RefreshIcon from '@mui/icons-material/Refresh';
import { Box, Button, CircularProgress, Typography } from '@mui/material';
import { MessageBox } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
interface FormLoaderProps {

View File

@@ -1,7 +1,8 @@
import { CircularProgress, Box, Typography } from '@mui/material';
import type { Theme } from '@mui/material';
import type { FC } from 'react';
import { Box, CircularProgress, Typography } from '@mui/material';
import type { Theme } from '@mui/material';
import { useI18nContext } from 'i18n/i18n-react';
interface LoadingSpinnerProps {

View File

@@ -1,7 +1,7 @@
import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
import type { FC } from 'react';
import type { Blocker } from 'react-router-dom';
import type { unstable_Blocker as Blocker } from 'react-router-dom';
import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
import { dialogStyle } from 'CustomTheme';
import { useI18nContext } from 'i18n/i18n-react';

View File

@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { Navigate } from 'react-router-dom';
import type { FC } from 'react';
import { Navigate } from 'react-router-dom';
import type { RequiredChildrenProps } from 'utils';
import { AuthenticatedContext } from 'contexts/authentication';
import type { RequiredChildrenProps } from 'utils';
const RequireAdmin: FC<RequiredChildrenProps> = ({ children }) => {
const authenticatedContext = useContext(AuthenticatedContext);

View File

@@ -1,12 +1,12 @@
import { useContext, useEffect } from 'react';
import type { FC } from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import type { AuthenticatedContextValue } from 'contexts/authentication/context';
import type { FC } from 'react';
import type { RequiredChildrenProps } from 'utils';
import { storeLoginRedirect } from 'api/authentication';
import type { AuthenticatedContextValue } from 'contexts/authentication/context';
import { AuthenticatedContext, AuthenticationContext } from 'contexts/authentication/context';
import type { RequiredChildrenProps } from 'utils';
const RequireAuthenticated: FC<RequiredChildrenProps> = ({ children }) => {
const authenticationContext = useContext(AuthenticationContext);

View File

@@ -1,10 +1,11 @@
import { useContext } from 'react';
import { Navigate } from 'react-router-dom';
import type { FC } from 'react';
import { Navigate } from 'react-router-dom';
import type { RequiredChildrenProps } from 'utils';
import * as AuthenticationApi from 'api/authentication';
import { AuthenticationContext } from 'contexts/authentication';
import type { RequiredChildrenProps } from 'utils';
const RequireUnauthenticated: FC<RequiredChildrenProps> = ({ children }) => {
const authenticationContext = useContext(AuthenticationContext);

View File

@@ -1,6 +1,7 @@
import { Tabs, useMediaQuery, useTheme } from '@mui/material';
import { useNavigate } from 'react-router-dom';
import type { FC } from 'react';
import { useNavigate } from 'react-router-dom';
import { Tabs, useMediaQuery, useTheme } from '@mui/material';
import type { RequiredChildrenProps } from 'utils';
@@ -14,7 +15,7 @@ const RouterTabs: FC<RouterTabsProps> = ({ value, children }) => {
const theme = useTheme();
const smallDown = useMediaQuery(theme.breakpoints.down('sm'));
const handleTabChange = (_event: any, path: string) => {
const handleTabChange = (_event: unknown, path: string) => {
navigate(path);
};

View File

@@ -1,13 +1,14 @@
import { Fragment } from 'react';
import type { FC } from 'react';
import { useDropzone } from 'react-dropzone';
import type { DropzoneState } from 'react-dropzone';
import CancelIcon from '@mui/icons-material/Cancel';
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
import { Box, Button, LinearProgress, Typography, useTheme } from '@mui/material';
import { Fragment } from 'react';
import { useDropzone } from 'react-dropzone';
import type { Theme } from '@mui/material';
import type { Progress } from 'alova';
import type { FC } from 'react';
import type { DropzoneState } from 'react-dropzone';
import type { Progress } from 'alova';
import { useI18nContext } from 'i18n/i18n-react';
const getBorderColor = (theme: Theme, props: DropzoneState) => {

View File

@@ -1,16 +1,18 @@
import { useRequest } from 'alova';
import { useCallback, useEffect, useState } from 'react';
import type { FC } from 'react';
import { redirect } from 'react-router-dom';
import { toast } from 'react-toastify';
import { AuthenticationContext } from './context';
import type { FC } from 'react';
import type { Me } from 'types';
import type { RequiredChildrenProps } from 'utils';
import * as AuthenticationApi from 'api/authentication';
import { ACCESS_TOKEN } from 'api/endpoints';
import { useRequest } from 'alova';
import { LoadingSpinner } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { Me } from 'types';
import type { RequiredChildrenProps } from 'utils';
import { AuthenticationContext } from './context';
const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
const { LL } = useI18nContext();
@@ -59,7 +61,6 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
setMe(undefined);
setInitialized(true);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {

View File

@@ -1,4 +1,5 @@
import { createContext } from 'react';
import type { Me } from 'types';
export interface AuthenticationContextValue {

View File

@@ -1,3 +1,6 @@
import { type FC, useState } from 'react';
import { toast } from 'react-toastify';
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import CancelIcon from '@mui/icons-material/Cancel';
import CastIcon from '@mui/icons-material/Cast';
@@ -10,18 +13,18 @@ import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore
import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import TuneIcon from '@mui/icons-material/Tune';
import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, List } from '@mui/material';
import { List, Button, Dialog, DialogActions, DialogContent, DialogTitle, Box } from '@mui/material';
import { useRequest } from 'alova';
import { useState, type FC } from 'react';
import { toast } from 'react-toastify';
import RestartMonitor from './system/RestartMonitor';
import { dialogStyle } from 'CustomTheme';
import * as SystemApi from 'api/system';
import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova';
import { ButtonRow, SectionContent, useLayoutTitle } from 'components';
import ListMenuItem from 'components/layout/ListMenuItem';
import { useI18nContext } from 'i18n/i18n-react';
import RestartMonitor from './system/RestartMonitor';
const Settings: FC = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.SETTINGS(0));
@@ -49,8 +52,8 @@ const Settings: FC = () => {
.then(() => {
setRestarting(true);
})
.catch((err) => {
toast.error(err.message);
.catch((error: Error) => {
toast.error(error.message);
})
.finally(() => {
setConfirmRestart(false);
@@ -64,8 +67,8 @@ const Settings: FC = () => {
.then(() => {
setRestarting(true);
})
.catch((err) => {
toast.error(err.message);
.catch((error: Error) => {
toast.error(error.message);
})
.finally(() => {
setConfirmFactoryReset(false);
@@ -79,8 +82,8 @@ const Settings: FC = () => {
.then(() => {
setRestarting(true);
})
.catch((err) => {
toast.error(err.message);
.catch((error: Error) => {
toast.error(error.message);
})
.finally(() => {
setConfirmRestart(false);

View File

@@ -1,27 +1,27 @@
import { useState } from 'react';
import type { FC } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning';
import { Button, Checkbox, MenuItem } from '@mui/material';
import { range } from 'lodash-es';
import { useState } from 'react';
import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react';
import type { APSettingsType } from 'types';
import * as APApi from 'api/ap';
import type { ValidateFieldsError } from 'async-validator';
import {
BlockFormControlLabel,
BlockNavigation,
ButtonRow,
FormLoader,
SectionContent,
ValidatedPasswordField,
ValidatedTextField,
BlockNavigation
ValidatedTextField
} 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';
import { createAPSettingsValidator, validate } from 'validators';
export const isAPEnabled = ({ provision_mode }: APSettingsType) =>
@@ -60,8 +60,8 @@ const APSettings: FC = () => {
setFieldErrors(undefined);
await validate(createAPSettingsValidator(data), data);
await saveData();
} catch (errors: any) {
setFieldErrors(errors);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
}
};

View File

@@ -1,17 +1,18 @@
import type { FC } from 'react';
import ComputerIcon from '@mui/icons-material/Computer';
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import RefreshIcon from '@mui/icons-material/Refresh';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material';
import { useRequest } from 'alova';
import type { Theme } from '@mui/material';
import type { FC } from 'react';
import type { APStatusType } from 'types';
import * as APApi from 'api/ap';
import { ButtonRow, FormLoader, SectionContent } from 'components';
import { useRequest } from 'alova';
import { ButtonRow, FormLoader, SectionContent } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { APStatusType } from 'types';
import { APNetworkStatus } from 'types';
export const apStatusHighlight = ({ status }: APStatusType, theme: Theme) => {

View File

@@ -1,12 +1,13 @@
import type { FC } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { Tab } from '@mui/material';
import { Navigate, Routes, Route } from 'react-router-dom';
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import APSettings from './APSettings';
import APStatus from './APStatus';
import type { FC } from 'react';
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
const AccessPoint: FC = () => {
const { LL } = useI18nContext();

View File

@@ -1,13 +1,14 @@
import { Tab } from '@mui/material';
import { Navigate, Route, Routes } from 'react-router-dom';
import MqttSettings from './MqttSettings';
import MqttStatus from './MqttStatus';
import type { FC } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { Tab } from '@mui/material';
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import MqttSettings from './MqttSettings';
import MqttStatus from './MqttStatus';
const Mqtt: FC = () => {
const { LL } = useI18nContext();

View File

@@ -1,24 +1,25 @@
import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning';
import { Button, Checkbox, MenuItem, Grid, Typography, InputAdornment, TextField } from '@mui/material';
import { useState } from 'react';
import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react';
import type { MqttSettingsType } from 'types';
import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning';
import { Button, Checkbox, Grid, InputAdornment, MenuItem, TextField, Typography } from '@mui/material';
import * as MqttApi from 'api/mqtt';
import type { ValidateFieldsError } from 'async-validator';
import {
BlockFormControlLabel,
BlockNavigation,
ButtonRow,
FormLoader,
SectionContent,
ValidatedPasswordField,
ValidatedTextField,
BlockNavigation
ValidatedTextField
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { MqttSettingsType } from 'types';
import { numberValue, updateValueDirty, useRest } from 'utils';
import { createMqttSettingsValidator, validate } from 'validators';
const MqttSettings: FC = () => {
@@ -54,8 +55,8 @@ const MqttSettings: FC = () => {
setFieldErrors(undefined);
await validate(createMqttSettingsValidator(data), data);
await saveData();
} catch (errors: any) {
setFieldErrors(errors);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
}
};

View File

@@ -1,17 +1,19 @@
import type { FC } from 'react';
import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion';
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import RefreshIcon from '@mui/icons-material/Refresh';
import ReportIcon from '@mui/icons-material/Report';
import SpeakerNotesOffIcon from '@mui/icons-material/SpeakerNotesOff';
import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material';
import { useRequest } from 'alova';
import type { Theme } from '@mui/material';
import type { FC } from 'react';
import type { MqttStatusType } from 'types';
import * as MqttApi from 'api/mqtt';
import { useRequest } from 'alova';
import { ButtonRow, FormLoader, SectionContent } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { MqttStatusType } from 'types';
import { MqttDisconnectReason } from 'types';
export const mqttStatusHighlight = ({ enabled, connected }: MqttStatusType, theme: Theme) => {

View File

@@ -1,15 +1,17 @@
import { Tab } from '@mui/material';
import { useCallback, useState } from 'react';
import { Navigate, Routes, Route, useNavigate } from 'react-router-dom';
import type { FC } from 'react';
import { Navigate, Route, Routes, useNavigate } from 'react-router-dom';
import { Tab } from '@mui/material';
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { WiFiNetwork } from 'types';
import NetworkSettings from './NetworkSettings';
import NetworkStatus from './NetworkStatus';
import { WiFiConnectionContext } from './WiFiConnectionContext';
import WiFiNetworkScanner from './WiFiNetworkScanner';
import type { FC } from 'react';
import type { WiFiNetwork } from 'types';
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
const Network: FC = () => {
const { LL } = useI18nContext();

View File

@@ -1,3 +1,7 @@
import { useContext, useEffect, useState } from 'react';
import type { FC } from 'react';
import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel';
import DeleteIcon from '@mui/icons-material/Delete';
import LockIcon from '@mui/icons-material/Lock';
@@ -14,39 +18,35 @@ import {
ListItemAvatar,
ListItemSecondaryAction,
ListItemText,
Typography,
MenuItem,
TextField,
MenuItem
Typography
} from '@mui/material';
// eslint-disable-next-line import/named
import * as NetworkApi from 'api/network';
import * as SystemApi from 'api/system';
import { updateState, useRequest } from 'alova';
import { useContext, useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import type { ValidateFieldsError } from 'async-validator';
import {
BlockFormControlLabel,
BlockNavigation,
ButtonRow,
FormLoader,
MessageBox,
SectionContent,
ValidatedPasswordField,
ValidatedTextField
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { NetworkSettingsType } from 'types';
import { updateValueDirty, useRest } from 'utils';
import { validate } from 'validators';
import { createNetworkSettingsValidator } from 'validators/network';
import RestartMonitor from '../system/RestartMonitor';
import { WiFiConnectionContext } from './WiFiConnectionContext';
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react';
import type { NetworkSettingsType } from 'types';
import * as NetworkApi from 'api/network';
import * as SystemApi from 'api/system';
import {
BlockFormControlLabel,
ButtonRow,
FormLoader,
SectionContent,
ValidatedPasswordField,
ValidatedTextField,
MessageBox,
BlockNavigation
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { updateValueDirty, useRest } from 'utils';
import { validate } from 'validators';
import { createNetworkSettingsValidator } from 'validators/network';
const NetworkSettings: FC = () => {
const { LL } = useI18nContext();
@@ -80,7 +80,7 @@ const NetworkSettings: FC = () => {
useEffect(() => {
if (!initialized && data) {
if (selectedNetwork) {
updateState('networkSettings', (current_data) => ({
updateState('networkSettings', (current_data: NetworkSettingsType) => ({
ssid: selectedNetwork.ssid,
bssid: selectedNetwork.bssid,
password: current_data ? current_data.password : '',
@@ -115,8 +115,8 @@ const NetworkSettings: FC = () => {
setFieldErrors(undefined);
await validate(createNetworkSettingsValidator(data), data);
await saveData();
} catch (errors: any) {
setFieldErrors(errors);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
}
deselectNetwork();
};
@@ -127,7 +127,7 @@ const NetworkSettings: FC = () => {
};
const restart = async () => {
await restartCommand().catch((error) => {
await restartCommand().catch((error: Error) => {
toast.error(error.message);
});
setRestarting(true);

View File

@@ -1,3 +1,5 @@
import type { FC } from 'react';
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import DnsIcon from '@mui/icons-material/Dns';
import GiteIcon from '@mui/icons-material/Gite';
@@ -7,15 +9,14 @@ import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import SettingsInputComponentIcon from '@mui/icons-material/SettingsInputComponent';
import WifiIcon from '@mui/icons-material/Wifi';
import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material';
import { useRequest } from 'alova';
import type { Theme } from '@mui/material';
import type { FC } from 'react';
import type { NetworkStatusType } from 'types';
import * as NetworkApi from 'api/network';
import { ButtonRow, FormLoader, SectionContent } from 'components';
import { useRequest } from 'alova';
import { ButtonRow, FormLoader, SectionContent } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { NetworkStatusType } from 'types';
import { NetworkConnectionStatus } from 'types';
const isConnected = ({ status }: NetworkStatusType) =>

View File

@@ -1,4 +1,5 @@
import { createContext } from 'react';
import type { WiFiNetwork } from 'types';
export interface WiFiConnectionContextValue {

View File

@@ -1,15 +1,16 @@
import { useRef, useState } from 'react';
import type { FC } from 'react';
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
import { Button } from '@mui/material';
// eslint-disable-next-line import/named
import * as NetworkApi from 'api/network';
import { updateState, useRequest } from 'alova';
import { useState, useRef } from 'react';
import { ButtonRow, FormLoader, SectionContent } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import WiFiNetworkSelector from './WiFiNetworkSelector';
import type { FC } from 'react';
import * as NetworkApi from 'api/network';
import { ButtonRow, FormLoader, SectionContent } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
const NUM_POLLS = 10;
const POLLING_FREQUENCY = 1000;

View File

@@ -1,17 +1,18 @@
import { useContext } from 'react';
import type { FC } from 'react';
import LockIcon from '@mui/icons-material/Lock';
import LockOpenIcon from '@mui/icons-material/LockOpen';
import WifiIcon from '@mui/icons-material/Wifi';
import { Avatar, Badge, List, ListItem, ListItemAvatar, ListItemIcon, ListItemText, useTheme } from '@mui/material';
import { useContext } from 'react';
import type { Theme } from '@mui/material';
import { MessageBox } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { WiFiNetwork, WiFiNetworkList } from 'types';
import { WiFiEncryptionType } from 'types';
import { WiFiConnectionContext } from './WiFiConnectionContext';
import type { Theme } from '@mui/material';
import type { FC } from 'react';
import type { WiFiNetwork, WiFiNetworkList } from 'types';
import { MessageBox } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { WiFiEncryptionType } from 'types';
interface WiFiNetworkSelectorProps {
networkList: WiFiNetworkList;
@@ -39,7 +40,7 @@ export const networkSecurityMode = ({ encryption_type }: WiFiNetwork) => {
case WiFiEncryptionType.WIFI_AUTH_WPA2_WPA3_PSK:
return 'WPA2/WPA3';
default:
return 'Unknown: ' + encryption_type;
return 'Unknown: ' + String(encryption_type);
}
};

View File

@@ -1,28 +1,30 @@
import { useState } from 'react';
import type { FC } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning';
import { Button, Checkbox, MenuItem } from '@mui/material';
// eslint-disable-next-line import/named
import { updateState } from 'alova';
import { useState } from 'react';
import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ';
import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react';
import type { NTPSettingsType } from 'types';
import * as NTPApi from 'api/ntp';
import { updateState } from 'alova';
import type { ValidateFieldsError } from 'async-validator';
import {
BlockFormControlLabel,
BlockNavigation,
ButtonRow,
FormLoader,
SectionContent,
ValidatedTextField,
BlockNavigation
ValidatedTextField
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { NTPSettingsType } from 'types';
import { updateValueDirty, useRest } from 'utils';
import { validate } from 'validators';
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
import { TIME_ZONES, selectedTimeZone, timeZoneSelectItems } from './TZ';
const NTPSettings: FC = () => {
const {
loadData,
@@ -56,15 +58,15 @@ const NTPSettings: FC = () => {
setFieldErrors(undefined);
await validate(NTP_SETTINGS_VALIDATOR, data);
await saveData();
} catch (errors: any) {
setFieldErrors(errors);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
}
};
const changeTimeZone = (event: React.ChangeEvent<HTMLInputElement>) => {
updateFormValue(event);
updateState('ntpSettings', (settings) => ({
updateState('ntpSettings', (settings: NTPSettingsType) => ({
...settings,
tz_label: event.target.value,
tz_format: TIME_ZONES[event.target.value]

View File

@@ -1,3 +1,7 @@
import { useState } from 'react';
import type { FC } from 'react';
import { toast } from 'react-toastify';
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import CancelIcon from '@mui/icons-material/Cancel';
import DnsIcon from '@mui/icons-material/Dns';
@@ -18,21 +22,18 @@ import {
ListItemAvatar,
ListItemText,
TextField,
useTheme,
Typography
Typography,
useTheme
} from '@mui/material';
import { useRequest } from 'alova';
import { useState } from 'react';
import { toast } from 'react-toastify';
import type { Theme } from '@mui/material';
import type { FC } from 'react';
import type { NTPStatusType } from 'types';
import { dialogStyle } from 'CustomTheme';
import * as NTPApi from 'api/ntp';
import { ButtonRow, FormLoader, SectionContent } from 'components';
import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova';
import { ButtonRow, FormLoader, SectionContent } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { NTPStatusType, Time } from 'types';
import { NTPSyncStatus } from 'types';
import { formatDateTime, formatLocalDateTime } from 'utils';
@@ -45,7 +46,7 @@ const NTPStatus: FC = () => {
const { LL } = useI18nContext();
const { send: updateTime } = useRequest((local_time) => NTPApi.updateTime(local_time), {
const { send: updateTime } = useRequest((local_time: Time) => NTPApi.updateTime(local_time), {
immediate: false
});

View File

@@ -1,13 +1,14 @@
import { Tab } from '@mui/material';
import { Navigate, Route, Routes } from 'react-router-dom';
import NTPSettings from './NTPSettings';
import NTPStatus from './NTPStatus';
import type { FC } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { Tab } from '@mui/material';
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import NTPSettings from './NTPSettings';
import NTPStatus from './NTPStatus';
const NetworkTime: FC = () => {
const { LL } = useI18nContext();
useLayoutTitle('NTP');

View File

@@ -1,8 +1,6 @@
import { MenuItem } from '@mui/material';
type TimeZones = {
[name: string]: string;
};
type TimeZones = Record<string, string>;
export const TIME_ZONES: TimeZones = {
'Africa/Abidjan': 'GMT0',

View File

@@ -1,26 +1,26 @@
import { useState } from 'react';
import type { FC } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning';
import { Button, Checkbox } from '@mui/material';
import { useState } from 'react';
import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react';
import type { OTASettingsType } from 'types';
import * as SystemApi from 'api/system';
import type { ValidateFieldsError } from 'async-validator';
import {
BlockFormControlLabel,
BlockNavigation,
ButtonRow,
FormLoader,
SectionContent,
ValidatedPasswordField,
ValidatedTextField,
BlockNavigation,
useLayoutTitle
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { OTASettingsType } from 'types';
import { numberValue, updateValueDirty, useRest } from 'utils';
import { validate } from 'validators';
import { OTA_SETTINGS_VALIDATOR } from 'validators/system';
@@ -57,8 +57,8 @@ const OTASettings: FC = () => {
setFieldErrors(undefined);
await validate(OTA_SETTINGS_VALIDATOR, data);
await saveData();
} catch (errors: any) {
setFieldErrors(errors);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
}
};

View File

@@ -1,23 +1,24 @@
import { useEffect } from 'react';
import type { FC } from 'react';
import CloseIcon from '@mui/icons-material/Close';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
LinearProgress,
Typography,
TextField,
Button
Typography
} from '@mui/material';
import { useRequest } from 'alova';
import { useEffect } from 'react';
import type { FC } from 'react';
import { dialogStyle } from 'CustomTheme';
import * as SecurityApi from 'api/security';
import { MessageBox } from 'components';
import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova';
import { MessageBox } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
interface GenerateTokenProps {
@@ -37,7 +38,6 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
if (open) {
void generateToken();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
return (

View File

@@ -1,3 +1,7 @@
import { useContext, useState } from 'react';
import type { FC } from 'react';
import { useBlocker } from 'react-router-dom';
import CancelIcon from '@mui/icons-material/Cancel';
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
@@ -6,24 +10,22 @@ import EditIcon from '@mui/icons-material/Edit';
import PersonAddIcon from '@mui/icons-material/PersonAdd';
import VpnKeyIcon from '@mui/icons-material/VpnKey';
import WarningIcon from '@mui/icons-material/Warning';
import { Button, IconButton, Box } from '@mui/material';
import { Box, Button, IconButton } 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 { useContext, useState } from 'react';
import { useBlocker } from 'react-router-dom';
import GenerateToken from './GenerateToken';
import User from './User';
import type { FC } from 'react';
import type { SecuritySettingsType, UserType } from 'types';
import * as SecurityApi from 'api/security';
import { ButtonRow, FormLoader, MessageBox, SectionContent, BlockNavigation } from 'components';
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 { BlockNavigation, ButtonRow, FormLoader, MessageBox, SectionContent } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import type { SecuritySettingsType, UserType } from 'types';
import { useRest } from 'utils';
import { createUserValidator } from 'validators';
import GenerateToken from './GenerateToken';
import User from './User';
const ManageUsers: FC = () => {
const { loadData, saveData, saving, data, updateDataValue, errorMessage } = useRest<SecuritySettingsType>({
read: SecurityApi.readSecuritySettings,
@@ -138,12 +140,20 @@ const ManageUsers: FC = () => {
setChanged(0);
};
const user_table = data.users.map((u) => ({ ...u, id: u.username }));
interface UserType2 {
id: string;
username: string;
password: string;
admin: boolean;
}
// add id to the type, needed for the table
const user_table = data.users.map((u) => ({ ...u, id: u.username })) as UserType2[];
return (
<>
<Table data={{ nodes: user_table }} theme={table_theme} layout={{ custom: true }}>
{(tableList: any) => (
{(tableList: UserType2[]) => (
<>
<Header>
<HeaderRow>
@@ -153,7 +163,7 @@ const ManageUsers: FC = () => {
</HeaderRow>
</Header>
<Body>
{tableList.map((u: any) => (
{tableList.map((u: UserType2) => (
<Row key={u.id} item={u}>
<Cell>{u.username}</Cell>
<Cell stiff>{u.admin ? <CheckIcon /> : <CloseIcon />}</Cell>

View File

@@ -1,12 +1,13 @@
import type { FC } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { Tab } from '@mui/material';
import { Navigate, Routes, Route } from 'react-router-dom';
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import ManageUsers from './ManageUsers';
import SecuritySettings from './SecuritySettings';
import type { FC } from 'react';
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
const Security: FC = () => {
const { LL } = useI18nContext();

View File

@@ -1,16 +1,17 @@
import { useContext, useState } from 'react';
import type { FC } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning';
import { Button } from '@mui/material';
import { useContext, useState } from 'react';
import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react';
import type { SecuritySettingsType } from 'types';
import * as SecurityApi from 'api/security';
import { ButtonRow, FormLoader, MessageBox, SectionContent, ValidatedPasswordField, BlockNavigation } from 'components';
import type { ValidateFieldsError } from 'async-validator';
import { BlockNavigation, ButtonRow, FormLoader, MessageBox, SectionContent, ValidatedPasswordField } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import type { SecuritySettingsType } from 'types';
import { updateValueDirty, useRest } from 'utils';
import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators';
@@ -49,8 +50,8 @@ const SecuritySettings: FC = () => {
await validate(SECURITY_SETTINGS_VALIDATOR, data);
await saveData();
await authenticatedContext.refresh();
} catch (errors: any) {
setFieldErrors(errors);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
}
};

View File

@@ -1,17 +1,17 @@
import { useEffect, useState } from 'react';
import type { FC } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import PersonAddIcon from '@mui/icons-material/PersonAdd';
import SaveIcon from '@mui/icons-material/Save';
import { Button, Checkbox, Dialog, DialogActions, DialogContent, DialogTitle } 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 type { FC } from 'react';
import type { UserType } from 'types';
import { dialogStyle } from 'CustomTheme';
import { BlockFormControlLabel, ValidatedPasswordField, ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { UserType } from 'types';
import { updateValue } from 'utils';
import { validate } from 'validators';
@@ -45,8 +45,8 @@ const User: FC<UserFormProps> = ({ creating, validator, user, setUser, onDoneEdi
setFieldErrors(undefined);
await validate(validator, user);
onDoneEditing();
} catch (errors: any) {
setFieldErrors(errors);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
}
}
};

View File

@@ -1,3 +1,5 @@
import type { FC } from 'react';
import AppsIcon from '@mui/icons-material/Apps';
import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard';
import DevicesIcon from '@mui/icons-material/Devices';
@@ -8,10 +10,9 @@ import SdCardAlertIcon from '@mui/icons-material/SdCardAlert';
import SdStorageIcon from '@mui/icons-material/SdStorage';
import { Avatar, Box, Button, Divider, List, ListItem, ListItemAvatar, ListItemText } from '@mui/material';
import { useRequest } from 'alova';
import type { FC } from 'react';
import * as SystemApi from 'api/system';
import { useRequest } from 'alova';
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';

View File

@@ -1,10 +1,10 @@
import { useRequest } from 'alova';
import { useRef, useState, useEffect } from 'react';
import { useEffect, useRef, useState } from 'react';
import type { FC } from 'react';
import * as SystemApi from 'api/system';
import { FormLoader } from 'components';
import { useRequest } from 'alova';
import { FormLoader } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
const RESTART_TIMEOUT = 2 * 60 * 1000;

View File

@@ -1,15 +1,16 @@
import { type FC, useContext } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { Tab } from '@mui/material';
import { useContext, type FC } from 'react';
import { Navigate, Routes, Route } from 'react-router-dom';
import SystemLog from './SystemLog';
import SystemStatus from './SystemStatus';
import { useRouterTab, RouterTabs, useLayoutTitle, RequireAdmin } from 'components';
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import SystemActivity from 'project/SystemActivity';
import SystemLog from './SystemLog';
import SystemStatus from './SystemStatus';
const System: FC = () => {
const { LL } = useI18nContext();

View File

@@ -1,24 +1,22 @@
import { useEffect, useRef, useState } from 'react';
import type { FC } from 'react';
import { toast } from 'react-toastify';
import DownloadIcon from '@mui/icons-material/GetApp';
import WarningIcon from '@mui/icons-material/Warning';
import { Box, styled, Button, Checkbox, MenuItem, Grid, TextField } from '@mui/material';
import { useRequest } from 'alova';
import { useState, useEffect, useRef } from 'react';
import { toast } from 'react-toastify';
import type { FC } from 'react';
import { Box, Button, Checkbox, Grid, MenuItem, TextField, styled } from '@mui/material';
import type { LogSettings, LogEntry } from 'types';
import { addAccessTokenParameter } from 'api/authentication';
import { EVENT_SOURCE_ROOT } from 'api/endpoints';
import * as SystemApi from 'api/system';
import { fetchLogES } from 'api/system';
import { SectionContent, FormLoader, BlockFormControlLabel, BlockNavigation, useLayoutTitle } from 'components';
import { useSSE } from '@alova/scene-react';
import { useRequest } from 'alova';
import { BlockFormControlLabel, BlockNavigation, FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { LogEntry, LogSettings } from 'types';
import { LogLevel } from 'types';
import { updateValueDirty, useRest } from 'utils';
export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log';
const LogEntryLine = styled('div')(() => ({
color: '#bbbbbb',
fontFamily: 'monospace',
@@ -58,13 +56,34 @@ const SystemLog: FC = () => {
update: SystemApi.updateLogSettings
});
// called on page load to reset pointer and fetch all log entries
useRequest(SystemApi.fetchLog());
const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
const [lastIndex, setLastIndex] = useState<number>(0);
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue);
// eslint-disable-next-line @typescript-eslint/unbound-method
const { onMessage, onError } = useSSE(fetchLogES, {
immediate: true,
// withCredentials: true,
interceptByGlobalResponded: false
});
onMessage((message: { id: number; data: string }) => {
const rawData = message.data;
const logentry = JSON.parse(rawData) as LogEntry;
if (logentry.i > lastIndex) {
setLastIndex(logentry.i);
setLogEntries((log) => [...log, logentry]);
}
});
onError(() => {
toast.error('No connection to Log server');
});
// called on page load to reset pointer and fetch all log entries
useRequest(SystemApi.fetchLog());
const paddedLevelLabel = (level: LogLevel) => {
const label = levelLabel(level);
return data?.compact ? ' ' + label[0] : label.padStart(8, '\xa0');
@@ -97,8 +116,8 @@ const SystemLog: FC = () => {
await saveData();
};
// handle scrolling
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (logEntries.length) {
ref.current?.scrollIntoView({
@@ -108,29 +127,6 @@ const SystemLog: FC = () => {
}
}, [logEntries.length]);
useEffect(() => {
const es = new EventSource(addAccessTokenParameter(LOG_EVENTSOURCE_URL));
es.onmessage = (event: MessageEvent) => {
const rawData = event.data;
if (typeof rawData === 'string' || rawData instanceof String) {
const logentry = JSON.parse(rawData as string) as LogEntry;
if (logentry.i > lastIndex) {
setLastIndex(logentry.i);
setLogEntries((log) => [...log, logentry]);
}
}
};
es.onerror = () => {
es.close();
toast.error('No connection to Log server');
};
return () => {
es.close();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;

View File

@@ -1,3 +1,6 @@
import { type FC, useContext, useState } from 'react';
import { toast } from 'react-toastify';
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import BuildIcon from '@mui/icons-material/Build';
import CancelIcon from '@mui/icons-material/Cancel';
@@ -9,7 +12,6 @@ import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
import RefreshIcon from '@mui/icons-material/Refresh';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import TimerIcon from '@mui/icons-material/Timer';
import {
Avatar,
Box,
@@ -26,17 +28,15 @@ import {
useTheme
} from '@mui/material';
import { useRequest } from 'alova';
import { useContext, type FC, useState } from 'react';
import { toast } from 'react-toastify';
import { dialogStyle } from 'CustomTheme';
import * as SystemApi from 'api/system';
import * as EMSESP from 'project/api';
import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova';
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import ListMenuItem from 'components/layout/ListMenuItem';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from 'project/api';
import { busConnectionStatus } from 'project/types';
import { NTPSyncStatus } from 'types';
@@ -141,8 +141,8 @@ const SystemStatus: FC = () => {
.then(() => {
toast.info(LL.SCANNING() + '...');
})
.catch((err) => {
toast.error(err.message);
.catch((error: Error) => {
toast.error(error.message);
});
setConfirmScan(false);
};

View File

@@ -1,15 +1,18 @@
import DownloadIcon from '@mui/icons-material/GetApp';
import { Typography, Button, Box, Link } from '@mui/material';
import { useRequest } from 'alova';
import { useState, type FC } from 'react';
import { type FC, useState } from 'react';
import { toast } from 'react-toastify';
import RestartMonitor from './RestartMonitor';
import DownloadIcon from '@mui/icons-material/GetApp';
import { Box, Button, Link, Typography } from '@mui/material';
import * as SystemApi from 'api/system';
import { FormLoader, SectionContent, SingleUpload, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from 'project/api';
import { useRequest } from 'alova';
import { FormLoader, SectionContent, SingleUpload, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { APIcall } from 'project/types';
import RestartMonitor from './RestartMonitor';
const UploadDownload: FC = () => {
const { LL } = useI18nContext();
@@ -28,7 +31,7 @@ const UploadDownload: FC = () => {
const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest(EMSESP.getSchedule(), {
immediate: false
});
const { send: getAPI, onSuccess: onGetAPI } = useRequest((data) => EMSESP.API(data), {
const { send: getAPI, onSuccess: onGetAPI } = useRequest((data: APIcall) => EMSESP.API(data), {
immediate: false
});
@@ -64,8 +67,9 @@ const UploadDownload: FC = () => {
force: true
});
onSuccessUpload(({ data }: any) => {
onSuccessUpload(({ data }) => {
if (data) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
setMd5(data.md5);
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
} else {
@@ -74,18 +78,18 @@ const UploadDownload: FC = () => {
});
const startUpload = async (files: File[]) => {
await sendUpload(files[0]).catch((err) => {
if (err.message === 'The user aborted a request') {
await sendUpload(files[0]).catch((error: Error) => {
if (error.message === 'The user aborted a request') {
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
} else if (err.message === 'Network Error') {
} else if (error.message === 'Network Error') {
toast.warning('Invalid file extension or incompatible bin file');
} else {
toast.error(err.message);
toast.error(error.message);
}
});
};
const saveFile = (json: any, endpoint: string) => {
const saveFile = (json: unknown, endpoint: string) => {
const anchor = document.createElement('a');
anchor.href = URL.createObjectURL(
new Blob([JSON.stringify(json, null, 2)], {
@@ -111,30 +115,31 @@ const UploadDownload: FC = () => {
saveFile(event.data, 'schedule.json');
});
onGetAPI((event) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
saveFile(event.data, event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt');
});
const downloadSettings = async () => {
await getSettings().catch((error) => {
await getSettings().catch((error: Error) => {
toast.error(error.message);
});
};
const downloadCustomizations = async () => {
await getCustomizations().catch((error) => {
await getCustomizations().catch((error: Error) => {
toast.error(error.message);
});
};
const downloadEntities = async () => {
await getEntities().catch((error) => {
await getEntities().catch((error: Error) => {
toast.error(error.message);
});
};
const downloadSchedule = async () => {
await getSchedule()
.catch((error) => {
.catch((error: Error) => {
toast.error(error.message);
})
.finally(() => {
@@ -143,7 +148,7 @@ const UploadDownload: 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);
});
};
@@ -173,7 +178,7 @@ const UploadDownload: FC = () => {
)&nbsp;(
<Link
target="_blank"
href={STABLE_URL + 'v' + latestVersion + '/' + getBinURL(latestVersion)}
href={STABLE_URL + 'v' + latestVersion + '/' + getBinURL(latestVersion as string)}
color="primary"
>
{LL.DOWNLOAD(1)}
@@ -190,7 +195,7 @@ const UploadDownload: FC = () => {
{LL.RELEASE_NOTES()}
</Link>
)&nbsp;(
<Link target="_blank" href={DEV_URL + getBinURL(latestDevVersion)} color="primary">
<Link target="_blank" href={DEV_URL + getBinURL(latestDevVersion as string)} color="primary">
{LL.DOWNLOAD(1)}
</Link>
)

View File

@@ -1,6 +1,6 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const de: Translation = {
LANGUAGE: 'Sprache',

View File

@@ -1,6 +1,6 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const en: Translation = {
LANGUAGE: 'Language',

View File

@@ -1,6 +1,7 @@
import type { Locales, Formatters } from './i18n-types';
import type { FormattersInitializer } from 'typesafe-i18n';
import type { Formatters, Locales } from './i18n-types';
export const initFormatters: FormattersInitializer<Locales, Formatters> = () => {
const formatters: Formatters = {
// add your formatter functions here

View File

@@ -1,6 +1,6 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const fr: Translation = {
LANGUAGE: 'Langue',

View File

@@ -1,6 +1,6 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const it: Translation = {
LANGUAGE: 'Lingua',

View File

@@ -1,6 +1,6 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const nl: Translation = {
LANGUAGE: 'Taal',
@@ -99,7 +99,7 @@ const nl: Translation = {
NUM_HOURS: '{num} {{uur|uren}}',
NUM_MINUTES: '{num} {{minuut|minuten}}',
APPLICATION_SETTINGS: 'Applicatieinstellingen',
CUSTOMIZATIONS: 'Custom aanpassingen',
CUSTOMIZATIONS: 'User Entities',
APPLICATION_RESTARTING: 'EMS-ESP herstarten',
INTERFACE_BOARD_PROFILE: 'Interface Apparaatprofiel',
BOARD_PROFILE_TEXT: 'Selecteer een vooraf ingesteld apparaat profiel uit de lijst of kies Eigen om zelf uw hardware te configureren',

View File

@@ -1,6 +1,6 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const no: Translation = {
LANGUAGE: 'Språk',
@@ -324,7 +324,12 @@ const no: Translation = {
UNCHANGED: 'Unchanged', // TODO translate
ALWAYS: 'Always', // TODO translate
ACTIVITY: 'Activity', // TODO translate
CONFIGURE: 'Configure {0}' // TODO translate
CONFIGURE: 'Configure {0}', // TODO translate
SYSTEM_MEMORY: 'System Memory', // TODO translate
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
CUSTOMIZE: 'Customize' // TODO translate
};
export default no;

View File

@@ -1,6 +1,6 @@
import type { BaseTranslation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const pl: BaseTranslation = {
LANGUAGE: 'Język',
@@ -287,8 +287,8 @@ const pl: BaseTranslation = {
NETWORK_SUBNET: 'Maska podsieci',
NETWORK_DNS: 'Serwery DNS',
ADDRESS_OF: 'Adres {0}',
ADMIN: 'Użytkownik "administrator".',
GUEST: 'Użytkownik "gość".',
ADMIN: 'Administrator',
GUEST: 'Gość',
NEW: 'nowe{{go|j|}}',
NEW_NAME_OF: 'Nowa nazwa {0}',
ENTITY: 'encji',

View File

@@ -1,6 +1,6 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const sk: Translation = {
LANGUAGE: 'Jazyk',

View File

@@ -1,6 +1,6 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const sv: Translation = {
LANGUAGE: 'Språk',

View File

@@ -1,6 +1,6 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const tr: Translation = {
LANGUAGE: 'Dil',

View File

@@ -1,6 +1,6 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { createBrowserRouter, createRoutesFromElements, Route, RouterProvider } from 'react-router-dom';
import { Route, RouterProvider, createBrowserRouter, createRoutesFromElements } from 'react-router-dom';
import App from 'App';

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), {
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), {
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), {
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), {
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;
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>

Some files were not shown because too many files have changed in this diff Show More