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 # standalone executable for testing
emsesp emsesp
interface/tsconfig.tsbuildinfo

View File

@@ -1,8 +1,13 @@
{ {
"plugins": ["@trivago/prettier-plugin-sort-imports"],
"trailingComma": "none", "trailingComma": "none",
"tabWidth": 2, "tabWidth": 2,
"semi": true, "semi": true,
"singleQuote": true, "singleQuote": true,
"printWidth": 120, "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": { "editor.codeActionsOnSave": {
"source.fixAll": "explicit" "source.fixAll": "explicit"
}, },
"eslint.validate": [
"typescript"
],
"eslint.codeActionsOnSave.rules": null,
"eslint.nodePath": "interface/.yarn/sdks", "eslint.nodePath": "interface/.yarn/sdks",
"eslint.workingDirectories": ["interface"], "eslint.workingDirectories": ["interface"],
"prettier.prettierPath": "", "prettier.prettierPath": "",
@@ -87,5 +91,6 @@
"cSpell.enableFiletypes": [ "cSpell.enableFiletypes": [
"!cpp", "!cpp",
"!typescript" "!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", "typesafe-i18n": "typesafe-i18n --no-watch",
"webUI": "node progmem-generator.js", "webUI": "node progmem-generator.js",
"format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'", "format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'",
"lint": "eslint . --cache --fix" "lint": "eslint . --fix"
}, },
"dependencies": { "dependencies": {
"@alova/adapter-xhr": "^1.0.3", "@alova/adapter-xhr": "^1.0.3",
"@alova/scene-react": "^1.5.0",
"@babel/core": "^7.24.4", "@babel/core": "^7.24.4",
"@emotion/react": "^11.11.4", "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5", "@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.15.15", "@mui/icons-material": "^5.15.15",
"@mui/material": "^5.15.15", "@mui/material": "^5.15.15",
"@table-library/react-table-library": "4.1.7", "@table-library/react-table-library": "4.1.7",
"@types/imagemin": "^8.0.5",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^20.12.7", "@types/node": "^20.12.7",
"@types/react": "^18.2.78", "@types/react": "^18.2.79",
"@types/react-dom": "^18.2.25", "@types/react-dom": "^18.2.25",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"alova": "^2.19.1", "alova": "^2.20.0",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"eslint-plugin-prettier": "^5.1.3",
"history": "^5.3.0", "history": "^5.3.0",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
@@ -52,23 +51,19 @@
"typescript": "^5.4.5" "typescript": "^5.4.5"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.1.1",
"@preact/compat": "^17.1.2", "@preact/compat": "^17.1.2",
"@preact/preset-vite": "^2.8.2", "@preact/preset-vite": "^2.8.2",
"@typescript-eslint/eslint-plugin": "^7.7.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/parser": "^7.7.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"eslint": "8.57.0", "eslint": "^9.1.0",
"eslint-config-prettier": "^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", "preact": "^10.20.2",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"terser": "^5.30.3", "terser": "^5.30.3",
"vite": "^5.2.8", "typescript-eslint": "^7.7.0",
"vite": "^5.2.10",
"vite-plugin-imagemin": "^0.6.1", "vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^4.3.2" "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 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 ARDUINO_INCLUDES = '#include <Arduino.h>\n\n';
const INDENT = ' '; const INDENT = ' ';

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
import { useContext, type FC } from 'react'; import { type FC, useContext } from 'react';
import { Navigate, Routes, Route } from 'react-router-dom'; import { Navigate, Route, Routes } from 'react-router-dom';
import Help from './project/Help';
import { Layout } from 'components'; import { Layout } from 'components';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import Settings from 'framework/Settings'; import Settings from 'framework/Settings';
@@ -21,6 +20,8 @@ import Devices from 'project/Devices';
import Scheduler from 'project/Scheduler'; import Scheduler from 'project/Scheduler';
import Sensors from 'project/Sensors'; import Sensors from 'project/Sensors';
import Help from './project/Help';
const AuthenticatedRouting: FC = () => { const AuthenticatedRouting: FC = () => {
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
return ( 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 type { FC } from 'react';
import { CssBaseline } from '@mui/material';
import { ThemeProvider, createTheme, responsiveFontSizes } from '@mui/material/styles';
import type { RequiredChildrenProps } from 'utils'; import type { RequiredChildrenProps } from 'utils';
export const dialogStyle = { 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 { 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 { 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 * as AuthenticationApi from 'api/authentication';
import { PROJECT_NAME } from 'api/env'; import { PROJECT_NAME } from 'api/env';
import { useRequest } from 'alova';
import type { ValidateFieldsError } from 'async-validator';
import { ValidatedPasswordField, ValidatedTextField } from 'components'; import { ValidatedPasswordField, ValidatedTextField } from 'components';
import { AuthenticationContext } from 'contexts/authentication'; import { AuthenticationContext } from 'contexts/authentication';
import DEflag from 'i18n/DE.svg'; import DEflag from 'i18n/DE.svg';
import FRflag from 'i18n/FR.svg'; import FRflag from 'i18n/FR.svg';
import GBflag from 'i18n/GB.svg'; import GBflag from 'i18n/GB.svg';
@@ -25,7 +23,9 @@ import SKflag from 'i18n/SK.svg';
import SVflag from 'i18n/SV.svg'; import SVflag from 'i18n/SV.svg';
import TRflag from 'i18n/TR.svg'; import TRflag from 'i18n/TR.svg';
import { I18nContext } from 'i18n/i18n-react'; import { I18nContext } from 'i18n/i18n-react';
import type { Locales } from 'i18n/i18n-types';
import { loadLocaleAsync } from 'i18n/i18n-util.async'; import { loadLocaleAsync } from 'i18n/i18n-util.async';
import type { SignInRequest } from 'types';
import { onEnterCallback, updateValue } from 'utils'; import { onEnterCallback, updateValue } from 'utils';
import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators'; import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators';
@@ -54,7 +54,7 @@ const SignIn: FC = () => {
const updateLoginRequestValue = updateValue(setSignInRequest); const updateLoginRequestValue = updateValue(setSignInRequest);
const signIn = async () => { const signIn = async () => {
await callSignIn(signInRequest).catch((event) => { await callSignIn(signInRequest).catch((event: Error) => {
if (event.message === 'Unauthorized') { if (event.message === 'Unauthorized') {
toast.warning(LL.INVALID_LOGIN()); toast.warning(LL.INVALID_LOGIN());
} else { } else {
@@ -72,8 +72,8 @@ const SignIn: FC = () => {
try { try {
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest); await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
await signIn(); await signIn();
} catch (errors: any) { } catch (error) {
setFieldErrors(errors); setFieldErrors(error as ValidateFieldsError);
setProcessing(false); setProcessing(false);
} }
}; };

View File

@@ -1,7 +1,7 @@
import type { APSettingsType, APStatusType } from 'types';
import { alovaInstance } from './endpoints'; import { alovaInstance } from './endpoints';
import type { APSettings, APStatus } from 'types'; export const readAPStatus = () => alovaInstance.Get<APStatusType>('/rest/apStatus');
export const readAPSettings = () => alovaInstance.Get<APSettingsType>('/rest/apSettings');
export const readAPStatus = () => alovaInstance.Get<APStatus>('/rest/apStatus'); export const updateAPSettings = (data: APSettingsType) => alovaInstance.Post<APSettingsType>('/rest/apSettings', data);
export const readAPSettings = () => alovaInstance.Get<APSettings>('/rest/apSettings');
export const updateAPSettings = (data: APSettings) => alovaInstance.Post<APSettings>('/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 { Path } from 'react-router-dom';
import type * as H from 'history';
import { jwtDecode } from 'jwt-decode';
import type { Me, SignInRequest, SignInResponse } from 'types'; import type { Me, SignInRequest, SignInResponse } from 'types';
import { ACCESS_TOKEN, alovaInstance } from './endpoints';
export const SIGN_IN_PATHNAME = 'loginPathname'; export const SIGN_IN_PATHNAME = 'loginPathname';
export const SIGN_IN_SEARCH = 'loginSearch'; export const SIGN_IN_SEARCH = 'loginSearch';

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,13 @@
import { alovaInstance } from './endpoints'; import type { NTPSettingsType, NTPStatusType, Time } from 'types';
import type { NTPSettings, NTPStatus, 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 = () => export const readNTPSettings = () =>
alovaInstance.Get<NTPSettings>('/rest/ntpSettings', { alovaInstance.Get<NTPSettingsType>('/rest/ntpSettings', {
name: '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); 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 { 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: SecuritySettingsType) =>
export const updateSecuritySettings = (securitySettings: SecuritySettings) =>
alovaInstance.Post('/rest/securitySettings', securitySettings); alovaInstance.Post('/rest/securitySettings', securitySettings);
export const generateToken = (username?: string) => 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 { alovaInstance, alovaInstanceGH } from './endpoints';
import type { OTASettings, SystemStatus, LogSettings, ESPSystemStatus } from 'types';
// ESPSystemStatus - also used to ping in Restart monitor for pinging // ESPSystemStatus - also used to ping in Restart monitor for pinging
export const readESPSystemStatus = () => alovaInstance.Get<ESPSystemStatus>('/rest/ESPSystemStatus'); export const readESPSystemStatus = () => alovaInstance.Get<ESPSystemStatus>('/rest/ESPSystemStatus');
@@ -14,23 +20,24 @@ export const factoryReset = () => alovaInstance.Post('/rest/factoryReset');
// OTA // OTA
export const readOTASettings = () => alovaInstance.Get<OTASettings>(`/rest/otaSettings`); 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 // SystemLog
export const readLogSettings = () => alovaInstance.Get<LogSettings>(`/rest/logSettings`); 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 fetchLog = () => alovaInstance.Post('/rest/fetchLog');
export const fetchLogES = () => alovaInstance.Get('/es/log');
// Get versions from github // Get versions from github
export const getStableVersion = () => export const getStableVersion = () =>
alovaInstanceGH.Get('latest', { alovaInstanceGH.Get('latest', {
transformData(response: any) { transformData(response) {
return response.data.name.substring(1); return response.data.name.substring(1);
} }
}); });
export const getDevVersion = () => export const getDevVersion = () =>
alovaInstanceGH.Get('tags/latest', { alovaInstanceGH.Get('tags/latest', {
transformData(response: any) { transformData(response) {
return response.data.name.split(/\s+/).splice(-1)[0].substring(1); 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 { Box } from '@mui/material';
import type { BoxProps } from '@mui/material'; import type { BoxProps } from '@mui/material';
import type { FC } from 'react';
const ButtonRow: FC<BoxProps> = ({ children, ...rest }) => ( const ButtonRow: FC<BoxProps> = ({ children, ...rest }) => (
<Box <Box

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,16 @@
import { Box, Toolbar } from '@mui/material'; import { useEffect, useState } from 'react';
import { useState, useEffect } from 'react'; import type { FC } from 'react';
import { useLocation } from 'react-router-dom'; 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 LayoutAppBar from './LayoutAppBar';
import LayoutDrawer from './LayoutDrawer'; import LayoutDrawer from './LayoutDrawer';
import { LayoutContext } from './context'; 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; export const DRAWER_WIDTH = 210;

View File

@@ -1,6 +1,7 @@
import type { FC } from 'react';
import MenuIcon from '@mui/icons-material/Menu'; import MenuIcon from '@mui/icons-material/Menu';
import { AppBar, IconButton, Toolbar, Typography } from '@mui/material'; import { AppBar, IconButton, Toolbar, Typography } from '@mui/material';
import type { FC } from 'react';
export const DRAWER_WIDTH = 210; 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 type { FC } from 'react';
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
import { PROJECT_NAME } from 'api/env'; import { PROJECT_NAME } from 'api/env';
import { DRAWER_WIDTH } from './Layout';
import LayoutMenu from './LayoutMenu';
const LayoutDrawerLogo = styled('img')(({ theme }) => ({ const LayoutDrawerLogo = styled('img')(({ theme }) => ({
[theme.breakpoints.down('sm')]: { [theme.breakpoints.down('sm')]: {
height: 24, 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 AccountCircleIcon from '@mui/icons-material/AccountCircle';
import AssessmentIcon from '@mui/icons-material/Assessment'; import AssessmentIcon from '@mui/icons-material/Assessment';
import CategoryIcon from '@mui/icons-material/Category'; 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 PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
import SensorsIcon from '@mui/icons-material/Sensors'; import SensorsIcon from '@mui/icons-material/Sensors';
import SettingsIcon from '@mui/icons-material/Settings'; import SettingsIcon from '@mui/icons-material/Settings';
import { import {
Divider, Avatar,
List,
Box, Box,
Button, Button,
Popover, Divider,
Avatar, List,
MenuItem,
TextField,
ListItem, ListItem,
ListItemButton, ListItemButton,
ListItemIcon, ListItemIcon,
ListItemText ListItemText,
MenuItem,
Popover,
TextField
} from '@mui/material'; } 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 LayoutMenuItem from 'components/layout/LayoutMenuItem';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import DEflag from 'i18n/DE.svg'; import DEflag from 'i18n/DE.svg';
import FRflag from 'i18n/FR.svg'; import FRflag from 'i18n/FR.svg';
import GBflag from 'i18n/GB.svg'; import GBflag from 'i18n/GB.svg';
@@ -41,8 +39,8 @@ import PLflag from 'i18n/PL.svg';
import SKflag from 'i18n/SK.svg'; import SKflag from 'i18n/SK.svg';
import SVflag from 'i18n/SV.svg'; import SVflag from 'i18n/SV.svg';
import TRflag from 'i18n/TR.svg'; import TRflag from 'i18n/TR.svg';
import { I18nContext } from 'i18n/i18n-react'; import { I18nContext } from 'i18n/i18n-react';
import type { Locales } from 'i18n/i18n-types';
import { loadLocaleAsync } from 'i18n/i18n-util.async'; import { loadLocaleAsync } from 'i18n/i18n-util.async';
const LayoutMenu: FC = () => { const LayoutMenu: FC = () => {
@@ -63,7 +61,7 @@ const LayoutMenu: FC = () => {
setLocale(loc); setLocale(loc);
}; };
const handleClick = (event: any) => { const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget); 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 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'; 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 NavigateNextIcon from '@mui/icons-material/NavigateNext';
import { Avatar, ListItem, ListItemAvatar, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; import { Avatar, ListItem, ListItemAvatar, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
import { Link } from 'react-router-dom';
import type { SvgIconProps } from '@mui/material'; import type { SvgIconProps } from '@mui/material';
import type { FC } from 'react';
interface ListMenuItemProps { interface ListMenuItemProps {
icon: React.ComponentType<SvgIconProps>; 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 { export interface LayoutContextValue {
title: string; title: string;

View File

@@ -1,6 +1,7 @@
import type { FC } from 'react';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { Box, Paper, Typography } from '@mui/material'; import { Box, Paper, Typography } from '@mui/material';
import type { FC } from 'react';
interface ApplicationErrorProps { interface ApplicationErrorProps {
message?: string; 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 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'; import { useI18nContext } from 'i18n/i18n-react';
interface FormLoaderProps { 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 type { FC } from 'react';
import { Box, CircularProgress, Typography } from '@mui/material';
import type { Theme } from '@mui/material';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
interface LoadingSpinnerProps { interface LoadingSpinnerProps {

View File

@@ -1,7 +1,7 @@
import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
import type { FC } from 'react'; 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 { dialogStyle } from 'CustomTheme';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';

View File

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

View File

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

View File

@@ -1,10 +1,11 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { Navigate } from 'react-router-dom';
import type { FC } from 'react'; import type { FC } from 'react';
import { Navigate } from 'react-router-dom';
import type { RequiredChildrenProps } from 'utils';
import * as AuthenticationApi from 'api/authentication'; import * as AuthenticationApi from 'api/authentication';
import { AuthenticationContext } from 'contexts/authentication'; import { AuthenticationContext } from 'contexts/authentication';
import type { RequiredChildrenProps } from 'utils';
const RequireUnauthenticated: FC<RequiredChildrenProps> = ({ children }) => { const RequireUnauthenticated: FC<RequiredChildrenProps> = ({ children }) => {
const authenticationContext = useContext(AuthenticationContext); 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 type { FC } from 'react';
import { useNavigate } from 'react-router-dom';
import { Tabs, useMediaQuery, useTheme } from '@mui/material';
import type { RequiredChildrenProps } from 'utils'; import type { RequiredChildrenProps } from 'utils';
@@ -14,7 +15,7 @@ const RouterTabs: FC<RouterTabsProps> = ({ value, children }) => {
const theme = useTheme(); const theme = useTheme();
const smallDown = useMediaQuery(theme.breakpoints.down('sm')); const smallDown = useMediaQuery(theme.breakpoints.down('sm'));
const handleTabChange = (_event: any, path: string) => { const handleTabChange = (_event: unknown, path: string) => {
navigate(path); 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 CancelIcon from '@mui/icons-material/Cancel';
import CloudUploadIcon from '@mui/icons-material/CloudUpload'; import CloudUploadIcon from '@mui/icons-material/CloudUpload';
import { Box, Button, LinearProgress, Typography, useTheme } from '@mui/material'; 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 { 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'; import { useI18nContext } from 'i18n/i18n-react';
const getBorderColor = (theme: Theme, props: DropzoneState) => { const getBorderColor = (theme: Theme, props: DropzoneState) => {

View File

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

View File

@@ -1,4 +1,5 @@
import { createContext } from 'react'; import { createContext } from 'react';
import type { Me } from 'types'; import type { Me } from 'types';
export interface AuthenticationContextValue { 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 AccessTimeIcon from '@mui/icons-material/AccessTime';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import CastIcon from '@mui/icons-material/Cast'; 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 SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import TuneIcon from '@mui/icons-material/Tune'; 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 * as SystemApi from 'api/system';
import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova';
import { ButtonRow, SectionContent, useLayoutTitle } from 'components'; import { ButtonRow, SectionContent, useLayoutTitle } from 'components';
import ListMenuItem from 'components/layout/ListMenuItem'; import ListMenuItem from 'components/layout/ListMenuItem';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import RestartMonitor from './system/RestartMonitor';
const Settings: FC = () => { const Settings: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle(LL.SETTINGS(0)); useLayoutTitle(LL.SETTINGS(0));
@@ -49,8 +52,8 @@ const Settings: FC = () => {
.then(() => { .then(() => {
setRestarting(true); setRestarting(true);
}) })
.catch((err) => { .catch((error: Error) => {
toast.error(err.message); toast.error(error.message);
}) })
.finally(() => { .finally(() => {
setConfirmRestart(false); setConfirmRestart(false);
@@ -64,8 +67,8 @@ const Settings: FC = () => {
.then(() => { .then(() => {
setRestarting(true); setRestarting(true);
}) })
.catch((err) => { .catch((error: Error) => {
toast.error(err.message); toast.error(error.message);
}) })
.finally(() => { .finally(() => {
setConfirmFactoryReset(false); setConfirmFactoryReset(false);
@@ -79,8 +82,8 @@ const Settings: FC = () => {
.then(() => { .then(() => {
setRestarting(true); setRestarting(true);
}) })
.catch((err) => { .catch((error: Error) => {
toast.error(err.message); toast.error(error.message);
}) })
.finally(() => { .finally(() => {
setConfirmRestart(false); 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 CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { Button, Checkbox, MenuItem } from '@mui/material'; import { Button, Checkbox, MenuItem } from '@mui/material';
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 * as APApi from 'api/ap';
import type { ValidateFieldsError } from 'async-validator';
import { import {
BlockFormControlLabel, BlockFormControlLabel,
BlockNavigation,
ButtonRow, ButtonRow,
FormLoader, FormLoader,
SectionContent, SectionContent,
ValidatedPasswordField, ValidatedPasswordField,
ValidatedTextField, ValidatedTextField
BlockNavigation
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { range } from 'lodash-es';
import type { APSettingsType } from 'types';
import { APProvisionMode } from 'types'; import { APProvisionMode } from 'types';
import { numberValue, updateValueDirty, useRest } from 'utils'; import { numberValue, updateValueDirty, useRest } from 'utils';
import { createAPSettingsValidator, validate } from 'validators'; import { createAPSettingsValidator, validate } from 'validators';
export const isAPEnabled = ({ provision_mode }: APSettingsType) => export const isAPEnabled = ({ provision_mode }: APSettingsType) =>
@@ -60,8 +60,8 @@ const APSettings: FC = () => {
setFieldErrors(undefined); setFieldErrors(undefined);
await validate(createAPSettingsValidator(data), data); await validate(createAPSettingsValidator(data), data);
await saveData(); await saveData();
} catch (errors: any) { } catch (error) {
setFieldErrors(errors); setFieldErrors(error as ValidateFieldsError);
} }
}; };

View File

@@ -1,17 +1,18 @@
import type { FC } from 'react';
import ComputerIcon from '@mui/icons-material/Computer'; import ComputerIcon from '@mui/icons-material/Computer';
import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material'; import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material';
import { useRequest } from 'alova';
import type { Theme } from '@mui/material'; import type { Theme } from '@mui/material';
import type { FC } from 'react';
import type { APStatusType } from 'types';
import * as APApi from 'api/ap'; 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 { useI18nContext } from 'i18n/i18n-react';
import type { APStatusType } from 'types';
import { APNetworkStatus } from 'types'; import { APNetworkStatus } from 'types';
export const apStatusHighlight = ({ status }: APStatusType, theme: Theme) => { 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 { 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 APSettings from './APSettings';
import APStatus from './APStatus'; 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 AccessPoint: FC = () => {
const { LL } = useI18nContext(); 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 type { FC } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { Tab } from '@mui/material';
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components'; import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import MqttSettings from './MqttSettings';
import MqttStatus from './MqttStatus';
const Mqtt: FC = () => { const Mqtt: FC = () => {
const { LL } = useI18nContext(); 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 { useState } from 'react';
import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react'; 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 * as MqttApi from 'api/mqtt';
import type { ValidateFieldsError } from 'async-validator';
import { import {
BlockFormControlLabel, BlockFormControlLabel,
BlockNavigation,
ButtonRow, ButtonRow,
FormLoader, FormLoader,
SectionContent, SectionContent,
ValidatedPasswordField, ValidatedPasswordField,
ValidatedTextField, ValidatedTextField
BlockNavigation
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import type { MqttSettingsType } from 'types';
import { numberValue, updateValueDirty, useRest } from 'utils'; import { numberValue, updateValueDirty, useRest } from 'utils';
import { createMqttSettingsValidator, validate } from 'validators'; import { createMqttSettingsValidator, validate } from 'validators';
const MqttSettings: FC = () => { const MqttSettings: FC = () => {
@@ -54,8 +55,8 @@ const MqttSettings: FC = () => {
setFieldErrors(undefined); setFieldErrors(undefined);
await validate(createMqttSettingsValidator(data), data); await validate(createMqttSettingsValidator(data), data);
await saveData(); await saveData();
} catch (errors: any) { } catch (error) {
setFieldErrors(errors); setFieldErrors(error as ValidateFieldsError);
} }
}; };

View File

@@ -1,17 +1,19 @@
import type { FC } from 'react';
import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion'; import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion';
import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import ReportIcon from '@mui/icons-material/Report'; import ReportIcon from '@mui/icons-material/Report';
import SpeakerNotesOffIcon from '@mui/icons-material/SpeakerNotesOff'; import SpeakerNotesOffIcon from '@mui/icons-material/SpeakerNotesOff';
import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material'; import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material';
import { useRequest } from 'alova';
import type { Theme } from '@mui/material'; import type { Theme } from '@mui/material';
import type { FC } from 'react';
import type { MqttStatusType } from 'types';
import * as MqttApi from 'api/mqtt'; import * as MqttApi from 'api/mqtt';
import { useRequest } from 'alova';
import { ButtonRow, FormLoader, SectionContent } from 'components'; import { ButtonRow, FormLoader, SectionContent } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import type { MqttStatusType } from 'types';
import { MqttDisconnectReason } from 'types'; import { MqttDisconnectReason } from 'types';
export const mqttStatusHighlight = ({ enabled, connected }: MqttStatusType, theme: Theme) => { 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 { 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 NetworkSettings from './NetworkSettings';
import NetworkStatus from './NetworkStatus'; import NetworkStatus from './NetworkStatus';
import { WiFiConnectionContext } from './WiFiConnectionContext'; import { WiFiConnectionContext } from './WiFiConnectionContext';
import WiFiNetworkScanner from './WiFiNetworkScanner'; 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 Network: FC = () => {
const { LL } = useI18nContext(); 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 CancelIcon from '@mui/icons-material/Cancel';
import DeleteIcon from '@mui/icons-material/Delete'; import DeleteIcon from '@mui/icons-material/Delete';
import LockIcon from '@mui/icons-material/Lock'; import LockIcon from '@mui/icons-material/Lock';
@@ -14,39 +18,35 @@ import {
ListItemAvatar, ListItemAvatar,
ListItemSecondaryAction, ListItemSecondaryAction,
ListItemText, ListItemText,
Typography, MenuItem,
TextField, TextField,
MenuItem Typography
} from '@mui/material'; } 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 { updateState, useRequest } from 'alova';
import { useContext, useEffect, useState } from 'react'; import type { ValidateFieldsError } from 'async-validator';
import { toast } from 'react-toastify'; 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 RestartMonitor from '../system/RestartMonitor';
import { WiFiConnectionContext } from './WiFiConnectionContext'; import { WiFiConnectionContext } from './WiFiConnectionContext';
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector'; 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 NetworkSettings: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
@@ -80,7 +80,7 @@ const NetworkSettings: FC = () => {
useEffect(() => { useEffect(() => {
if (!initialized && data) { if (!initialized && data) {
if (selectedNetwork) { if (selectedNetwork) {
updateState('networkSettings', (current_data) => ({ updateState('networkSettings', (current_data: NetworkSettingsType) => ({
ssid: selectedNetwork.ssid, ssid: selectedNetwork.ssid,
bssid: selectedNetwork.bssid, bssid: selectedNetwork.bssid,
password: current_data ? current_data.password : '', password: current_data ? current_data.password : '',
@@ -115,8 +115,8 @@ const NetworkSettings: FC = () => {
setFieldErrors(undefined); setFieldErrors(undefined);
await validate(createNetworkSettingsValidator(data), data); await validate(createNetworkSettingsValidator(data), data);
await saveData(); await saveData();
} catch (errors: any) { } catch (error) {
setFieldErrors(errors); setFieldErrors(error as ValidateFieldsError);
} }
deselectNetwork(); deselectNetwork();
}; };
@@ -127,7 +127,7 @@ const NetworkSettings: FC = () => {
}; };
const restart = async () => { const restart = async () => {
await restartCommand().catch((error) => { await restartCommand().catch((error: Error) => {
toast.error(error.message); toast.error(error.message);
}); });
setRestarting(true); setRestarting(true);

View File

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

View File

@@ -1,4 +1,5 @@
import { createContext } from 'react'; import { createContext } from 'react';
import type { WiFiNetwork } from 'types'; import type { WiFiNetwork } from 'types';
export interface WiFiConnectionContextValue { 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 PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
// eslint-disable-next-line import/named
import * as NetworkApi from 'api/network';
import { updateState, useRequest } from 'alova'; 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 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 NUM_POLLS = 10;
const POLLING_FREQUENCY = 1000; 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 LockIcon from '@mui/icons-material/Lock';
import LockOpenIcon from '@mui/icons-material/LockOpen'; import LockOpenIcon from '@mui/icons-material/LockOpen';
import WifiIcon from '@mui/icons-material/Wifi'; import WifiIcon from '@mui/icons-material/Wifi';
import { Avatar, Badge, List, ListItem, ListItemAvatar, ListItemIcon, ListItemText, useTheme } from '@mui/material'; 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 { 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 { interface WiFiNetworkSelectorProps {
networkList: WiFiNetworkList; networkList: WiFiNetworkList;
@@ -39,7 +40,7 @@ export const networkSecurityMode = ({ encryption_type }: WiFiNetwork) => {
case WiFiEncryptionType.WIFI_AUTH_WPA2_WPA3_PSK: case WiFiEncryptionType.WIFI_AUTH_WPA2_WPA3_PSK:
return 'WPA2/WPA3'; return 'WPA2/WPA3';
default: 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 CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { Button, Checkbox, MenuItem } from '@mui/material'; import { Button, Checkbox, MenuItem } from '@mui/material';
// eslint-disable-next-line import/named
import { updateState } from 'alova';
import { 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 * as NTPApi from 'api/ntp';
import { updateState } from 'alova';
import type { ValidateFieldsError } from 'async-validator';
import { import {
BlockFormControlLabel, BlockFormControlLabel,
BlockNavigation,
ButtonRow, ButtonRow,
FormLoader, FormLoader,
SectionContent, SectionContent,
ValidatedTextField, ValidatedTextField
BlockNavigation
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import type { NTPSettingsType } from 'types';
import { updateValueDirty, useRest } from 'utils'; import { updateValueDirty, useRest } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp'; import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
import { TIME_ZONES, selectedTimeZone, timeZoneSelectItems } from './TZ';
const NTPSettings: FC = () => { const NTPSettings: FC = () => {
const { const {
loadData, loadData,
@@ -56,15 +58,15 @@ const NTPSettings: FC = () => {
setFieldErrors(undefined); setFieldErrors(undefined);
await validate(NTP_SETTINGS_VALIDATOR, data); await validate(NTP_SETTINGS_VALIDATOR, data);
await saveData(); await saveData();
} catch (errors: any) { } catch (error) {
setFieldErrors(errors); setFieldErrors(error as ValidateFieldsError);
} }
}; };
const changeTimeZone = (event: React.ChangeEvent<HTMLInputElement>) => { const changeTimeZone = (event: React.ChangeEvent<HTMLInputElement>) => {
updateFormValue(event); updateFormValue(event);
updateState('ntpSettings', (settings) => ({ updateState('ntpSettings', (settings: NTPSettingsType) => ({
...settings, ...settings,
tz_label: event.target.value, tz_label: event.target.value,
tz_format: TIME_ZONES[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 AccessTimeIcon from '@mui/icons-material/AccessTime';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import DnsIcon from '@mui/icons-material/Dns'; import DnsIcon from '@mui/icons-material/Dns';
@@ -18,21 +22,18 @@ import {
ListItemAvatar, ListItemAvatar,
ListItemText, ListItemText,
TextField, TextField,
useTheme, Typography,
Typography useTheme
} from '@mui/material'; } from '@mui/material';
import { useRequest } from 'alova';
import { useState } from 'react';
import { toast } from 'react-toastify';
import type { Theme } from '@mui/material'; 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 * 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 { useI18nContext } from 'i18n/i18n-react';
import type { NTPStatusType, Time } from 'types';
import { NTPSyncStatus } from 'types'; import { NTPSyncStatus } from 'types';
import { formatDateTime, formatLocalDateTime } from 'utils'; import { formatDateTime, formatLocalDateTime } from 'utils';
@@ -45,7 +46,7 @@ const NTPStatus: FC = () => {
const { LL } = useI18nContext(); 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 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 type { FC } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { Tab } from '@mui/material';
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components'; import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import NTPSettings from './NTPSettings';
import NTPStatus from './NTPStatus';
const NetworkTime: FC = () => { const NetworkTime: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle('NTP'); useLayoutTitle('NTP');

View File

@@ -1,8 +1,6 @@
import { MenuItem } from '@mui/material'; import { MenuItem } from '@mui/material';
type TimeZones = { type TimeZones = Record<string, string>;
[name: string]: string;
};
export const TIME_ZONES: TimeZones = { export const TIME_ZONES: TimeZones = {
'Africa/Abidjan': 'GMT0', '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 CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { Button, Checkbox } from '@mui/material'; 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 * as SystemApi from 'api/system';
import type { ValidateFieldsError } from 'async-validator';
import { import {
BlockFormControlLabel, BlockFormControlLabel,
BlockNavigation,
ButtonRow, ButtonRow,
FormLoader, FormLoader,
SectionContent, SectionContent,
ValidatedPasswordField, ValidatedPasswordField,
ValidatedTextField, ValidatedTextField,
BlockNavigation,
useLayoutTitle useLayoutTitle
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import type { OTASettingsType } from 'types';
import { numberValue, updateValueDirty, useRest } from 'utils'; import { numberValue, updateValueDirty, useRest } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
import { OTA_SETTINGS_VALIDATOR } from 'validators/system'; import { OTA_SETTINGS_VALIDATOR } from 'validators/system';
@@ -57,8 +57,8 @@ const OTASettings: FC = () => {
setFieldErrors(undefined); setFieldErrors(undefined);
await validate(OTA_SETTINGS_VALIDATOR, data); await validate(OTA_SETTINGS_VALIDATOR, data);
await saveData(); await saveData();
} catch (errors: any) { } catch (error) {
setFieldErrors(errors); 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 CloseIcon from '@mui/icons-material/Close';
import { import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Box, Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
LinearProgress, LinearProgress,
Typography,
TextField, TextField,
Button Typography
} from '@mui/material'; } 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 * 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'; import { useI18nContext } from 'i18n/i18n-react';
interface GenerateTokenProps { interface GenerateTokenProps {
@@ -37,7 +38,6 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
if (open) { if (open) {
void generateToken(); void generateToken();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]); }, [open]);
return ( 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 CancelIcon from '@mui/icons-material/Cancel';
import CheckIcon from '@mui/icons-material/Check'; import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close'; 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 PersonAddIcon from '@mui/icons-material/PersonAdd';
import VpnKeyIcon from '@mui/icons-material/VpnKey'; import VpnKeyIcon from '@mui/icons-material/VpnKey';
import WarningIcon from '@mui/icons-material/Warning'; 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 * 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 { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import type { SecuritySettingsType, UserType } from 'types';
import { useRest } from 'utils'; import { useRest } from 'utils';
import { createUserValidator } from 'validators'; import { createUserValidator } from 'validators';
import GenerateToken from './GenerateToken';
import User from './User';
const ManageUsers: FC = () => { const ManageUsers: FC = () => {
const { loadData, saveData, saving, data, updateDataValue, errorMessage } = useRest<SecuritySettingsType>({ const { loadData, saveData, saving, data, updateDataValue, errorMessage } = useRest<SecuritySettingsType>({
read: SecurityApi.readSecuritySettings, read: SecurityApi.readSecuritySettings,
@@ -138,12 +140,20 @@ const ManageUsers: FC = () => {
setChanged(0); 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 ( return (
<> <>
<Table data={{ nodes: user_table }} theme={table_theme} layout={{ custom: true }}> <Table data={{ nodes: user_table }} theme={table_theme} layout={{ custom: true }}>
{(tableList: any) => ( {(tableList: UserType2[]) => (
<> <>
<Header> <Header>
<HeaderRow> <HeaderRow>
@@ -153,7 +163,7 @@ const ManageUsers: FC = () => {
</HeaderRow> </HeaderRow>
</Header> </Header>
<Body> <Body>
{tableList.map((u: any) => ( {tableList.map((u: UserType2) => (
<Row key={u.id} item={u}> <Row key={u.id} item={u}>
<Cell>{u.username}</Cell> <Cell>{u.username}</Cell>
<Cell stiff>{u.admin ? <CheckIcon /> : <CloseIcon />}</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 { 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 ManageUsers from './ManageUsers';
import SecuritySettings from './SecuritySettings'; 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 Security: FC = () => {
const { LL } = useI18nContext(); 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 CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { Button } from '@mui/material'; 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 * 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 { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import type { SecuritySettingsType } from 'types';
import { updateValueDirty, useRest } from 'utils'; import { updateValueDirty, useRest } from 'utils';
import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators'; import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators';
@@ -49,8 +50,8 @@ const SecuritySettings: FC = () => {
await validate(SECURITY_SETTINGS_VALIDATOR, data); await validate(SECURITY_SETTINGS_VALIDATOR, data);
await saveData(); await saveData();
await authenticatedContext.refresh(); await authenticatedContext.refresh();
} catch (errors: any) { } catch (error) {
setFieldErrors(errors); 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 CancelIcon from '@mui/icons-material/Cancel';
import PersonAddIcon from '@mui/icons-material/PersonAdd'; import PersonAddIcon from '@mui/icons-material/PersonAdd';
import SaveIcon from '@mui/icons-material/Save'; import SaveIcon from '@mui/icons-material/Save';
import { Button, Checkbox, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material'; 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 Schema from 'async-validator';
import type { ValidateFieldsError } 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 { BlockFormControlLabel, ValidatedPasswordField, ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import type { UserType } from 'types';
import { updateValue } from 'utils'; import { updateValue } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
@@ -45,8 +45,8 @@ const User: FC<UserFormProps> = ({ creating, validator, user, setUser, onDoneEdi
setFieldErrors(undefined); setFieldErrors(undefined);
await validate(validator, user); await validate(validator, user);
onDoneEditing(); onDoneEditing();
} catch (errors: any) { } catch (error) {
setFieldErrors(errors); setFieldErrors(error as ValidateFieldsError);
} }
} }
}; };

View File

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

View File

@@ -1,10 +1,10 @@
import { useRequest } from 'alova'; import { useEffect, useRef, useState } from 'react';
import { useRef, useState, useEffect } from 'react';
import type { FC } from 'react'; import type { FC } from 'react';
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { FormLoader } from 'components';
import { useRequest } from 'alova';
import { FormLoader } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
const RESTART_TIMEOUT = 2 * 60 * 1000; 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 { 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 { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import SystemActivity from 'project/SystemActivity'; import SystemActivity from 'project/SystemActivity';
import SystemLog from './SystemLog';
import SystemStatus from './SystemStatus';
const System: FC = () => { const System: FC = () => {
const { LL } = useI18nContext(); 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 DownloadIcon from '@mui/icons-material/GetApp';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { Box, styled, Button, Checkbox, MenuItem, Grid, TextField } from '@mui/material'; import { Box, Button, Checkbox, Grid, MenuItem, TextField, styled } from '@mui/material';
import { useRequest } from 'alova';
import { useState, useEffect, useRef } from 'react';
import { toast } from 'react-toastify';
import type { FC } from 'react';
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 * 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 { useI18nContext } from 'i18n/i18n-react';
import type { LogEntry, LogSettings } from 'types';
import { LogLevel } from 'types'; import { LogLevel } from 'types';
import { updateValueDirty, useRest } from 'utils'; import { updateValueDirty, useRest } from 'utils';
export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log';
const LogEntryLine = styled('div')(() => ({ const LogEntryLine = styled('div')(() => ({
color: '#bbbbbb', color: '#bbbbbb',
fontFamily: 'monospace', fontFamily: 'monospace',
@@ -58,13 +56,34 @@ const SystemLog: FC = () => {
update: SystemApi.updateLogSettings update: SystemApi.updateLogSettings
}); });
// called on page load to reset pointer and fetch all log entries
useRequest(SystemApi.fetchLog());
const [logEntries, setLogEntries] = useState<LogEntry[]>([]); const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
const [lastIndex, setLastIndex] = useState<number>(0); const [lastIndex, setLastIndex] = useState<number>(0);
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue); const 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 paddedLevelLabel = (level: LogLevel) => {
const label = levelLabel(level); const label = levelLabel(level);
return data?.compact ? ' ' + label[0] : label.padStart(8, '\xa0'); return data?.compact ? ' ' + label[0] : label.padStart(8, '\xa0');
@@ -97,8 +116,8 @@ const SystemLog: FC = () => {
await saveData(); await saveData();
}; };
// handle scrolling
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
if (logEntries.length) { if (logEntries.length) {
ref.current?.scrollIntoView({ ref.current?.scrollIntoView({
@@ -108,29 +127,6 @@ const SystemLog: FC = () => {
} }
}, [logEntries.length]); }, [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 = () => { const content = () => {
if (!data) { if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />; 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 AccessTimeIcon from '@mui/icons-material/AccessTime';
import BuildIcon from '@mui/icons-material/Build'; import BuildIcon from '@mui/icons-material/Build';
import CancelIcon from '@mui/icons-material/Cancel'; 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 RefreshIcon from '@mui/icons-material/Refresh';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import TimerIcon from '@mui/icons-material/Timer'; import TimerIcon from '@mui/icons-material/Timer';
import { import {
Avatar, Avatar,
Box, Box,
@@ -26,17 +28,15 @@ import {
useTheme useTheme
} from '@mui/material'; } 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 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 { FormLoader, SectionContent, useLayoutTitle } from 'components';
import ListMenuItem from 'components/layout/ListMenuItem'; import ListMenuItem from 'components/layout/ListMenuItem';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from 'project/api';
import { busConnectionStatus } from 'project/types'; import { busConnectionStatus } from 'project/types';
import { NTPSyncStatus } from 'types'; import { NTPSyncStatus } from 'types';
@@ -141,8 +141,8 @@ const SystemStatus: FC = () => {
.then(() => { .then(() => {
toast.info(LL.SCANNING() + '...'); toast.info(LL.SCANNING() + '...');
}) })
.catch((err) => { .catch((error: Error) => {
toast.error(err.message); toast.error(error.message);
}); });
setConfirmScan(false); setConfirmScan(false);
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import type { Translation } from '../i18n-types'; import type { Translation } from '../i18n-types';
/* prettier-ignore */ /* prettier-ignore */
/* eslint-disable */
const nl: Translation = { const nl: Translation = {
LANGUAGE: 'Taal', LANGUAGE: 'Taal',
@@ -99,7 +99,7 @@ const nl: Translation = {
NUM_HOURS: '{num} {{uur|uren}}', NUM_HOURS: '{num} {{uur|uren}}',
NUM_MINUTES: '{num} {{minuut|minuten}}', NUM_MINUTES: '{num} {{minuut|minuten}}',
APPLICATION_SETTINGS: 'Applicatieinstellingen', APPLICATION_SETTINGS: 'Applicatieinstellingen',
CUSTOMIZATIONS: 'Custom aanpassingen', CUSTOMIZATIONS: 'User Entities',
APPLICATION_RESTARTING: 'EMS-ESP herstarten', APPLICATION_RESTARTING: 'EMS-ESP herstarten',
INTERFACE_BOARD_PROFILE: 'Interface Apparaatprofiel', 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', 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'; import type { Translation } from '../i18n-types';
/* prettier-ignore */ /* prettier-ignore */
/* eslint-disable */
const no: Translation = { const no: Translation = {
LANGUAGE: 'Språk', LANGUAGE: 'Språk',
@@ -324,7 +324,12 @@ const no: Translation = {
UNCHANGED: 'Unchanged', // TODO translate UNCHANGED: 'Unchanged', // TODO translate
ALWAYS: 'Always', // TODO translate ALWAYS: 'Always', // TODO translate
ACTIVITY: 'Activity', // 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; export default no;

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import { StrictMode } from 'react'; import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client'; 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'; 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 CancelIcon from '@mui/icons-material/Cancel';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment, TextField } from '@mui/material'; import { Box, Button, Checkbox, Divider, Grid, InputAdornment, MenuItem, TextField, Typography } from '@mui/material';
import { useRequest } from 'alova';
import { useState } from 'react';
import { toast } from 'react-toastify';
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 * as SystemApi from 'api/system';
import { useRequest } from 'alova';
import type { ValidateFieldsError } from 'async-validator';
import { import {
SectionContent,
FormLoader,
BlockFormControlLabel, BlockFormControlLabel,
ValidatedTextField,
ButtonRow,
MessageBox,
BlockNavigation, BlockNavigation,
ButtonRow,
FormLoader,
MessageBox,
SectionContent,
ValidatedTextField,
useLayoutTitle useLayoutTitle
} from 'components'; } from 'components';
import RestartMonitor from 'framework/system/RestartMonitor'; import RestartMonitor from 'framework/system/RestartMonitor';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValueDirty, useRest } from 'utils'; import { numberValue, updateValueDirty, useRest } from 'utils';
import { validate } from 'validators'; 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() { export function boardProfileSelectItems() {
return Object.keys(BOARD_PROFILES).map((code) => ( return Object.keys(BOARD_PROFILES).map((code) => (
<MenuItem key={code} value={code}> <MenuItem key={code} value={code}>
@@ -67,7 +69,7 @@ const ApplicationSettings: FC = () => {
loading: processingBoard, loading: processingBoard,
send: readBoardProfile, send: readBoardProfile,
onSuccess: onSuccessBoardProfile onSuccess: onSuccessBoardProfile
} = useRequest((boardProfile) => EMSESP.getBoardProfile(boardProfile), { } = useRequest((boardProfile: string) => EMSESP.getBoardProfile(boardProfile), {
immediate: false immediate: false
}); });
@@ -93,7 +95,7 @@ const ApplicationSettings: FC = () => {
}); });
const updateBoardProfile = async (board_profile: string) => { const updateBoardProfile = async (board_profile: string) => {
await readBoardProfile(board_profile).catch((error) => { await readBoardProfile(board_profile).catch((error: Error) => {
toast.error(error.message); toast.error(error.message);
}); });
}; };
@@ -109,8 +111,8 @@ const ApplicationSettings: FC = () => {
try { try {
setFieldErrors(undefined); setFieldErrors(undefined);
await validate(createSettingsValidator(data), data); await validate(createSettingsValidator(data), data);
} catch (errors: any) { } catch (error) {
setFieldErrors(errors); setFieldErrors(error as ValidateFieldsError);
} finally { } finally {
await saveData(); await saveData();
} }
@@ -131,7 +133,7 @@ const ApplicationSettings: FC = () => {
const restart = async () => { const restart = async () => {
await validateAndSubmit(); await validateAndSubmit();
await restartCommand().catch((error) => { await restartCommand().catch((error: Error) => {
toast.error(error.message); toast.error(error.message);
}); });
setRestarting(true); 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 AddIcon from '@mui/icons-material/Add';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { Button, Typography, Box } from '@mui/material'; import { Box, Button, Typography } 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'; import { useTheme } from '@table-library/react-table-library/theme';
// eslint-disable-next-line import/named
import { updateState, useRequest } from 'alova'; import { updateState, useRequest } from 'alova';
import { useState, useCallback } from 'react'; import { BlockNavigation, ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
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 { useI18nContext } from 'i18n/i18n-react'; 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 CustomEntities: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0); const [numChanges, setNumChanges] = useState<number>(0);
@@ -42,7 +41,10 @@ const CustomEntities: FC = () => {
force: true 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) { function hasEntityChanged(ei: EntityItem) {
return ( return (
@@ -139,8 +141,8 @@ const CustomEntities: FC = () => {
.then(() => { .then(() => {
toast.success(LL.ENTITIES_UPDATED()); toast.success(LL.ENTITIES_UPDATED());
}) })
.catch((err) => { .catch((error: Error) => {
toast.error(err.message); toast.error(error.message);
}) })
.finally(async () => { .finally(async () => {
await fetchEntities(); await fetchEntities();
@@ -167,7 +169,7 @@ const CustomEntities: FC = () => {
const onDialogSave = (updatedItem: EntityItem) => { const onDialogSave = (updatedItem: EntityItem) => {
setDialogOpen(false); setDialogOpen(false);
updateState('entities', (data) => { updateState('entities', (data: EntityItem[]) => {
const new_data = creating const new_data = creating
? [...data.filter((ei) => creating || ei.o_id !== updatedItem.o_id), updatedItem] ? [...data.filter((ei) => creating || ei.o_id !== updatedItem.o_id), updatedItem]
: data.map((ei) => (ei.id === updatedItem.id ? { ...ei, ...updatedItem } : ei)); : data.map((ei) => (ei.id === updatedItem.id ? { ...ei, ...updatedItem } : ei));
@@ -195,12 +197,12 @@ const CustomEntities: FC = () => {
setDialogOpen(true); setDialogOpen(true);
}; };
function formatValue(value: any, uom: number) { function formatValue(value: unknown, uom: number) {
return value === undefined || uom === undefined return value === undefined
? '' ? ''
: typeof value === 'number' : typeof value === 'number'
? new Intl.NumberFormat().format(value) + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom]) ? new Intl.NumberFormat().format(value) + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom])
: value; : (value as string);
} }
function showHex(value: number, digit: number) { function showHex(value: number, digit: number) {
@@ -214,7 +216,7 @@ const CustomEntities: FC = () => {
return ( return (
<Table data={{ nodes: entities.filter((ei) => !ei.deleted) }} theme={entity_theme} layout={{ custom: true }}> <Table data={{ nodes: entities.filter((ei) => !ei.deleted) }} theme={entity_theme} layout={{ custom: true }}>
{(tableList: any) => ( {(tableList: EntityItem[]) => (
<> <>
<Header> <Header>
<HeaderRow> <HeaderRow>

View File

@@ -1,3 +1,5 @@
import { useEffect, useState } from 'react';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import DoneIcon from '@mui/icons-material/Done'; import DoneIcon from '@mui/icons-material/Done';
@@ -15,29 +17,26 @@ import {
MenuItem, MenuItem,
TextField TextField
} from '@mui/material'; } 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 { dialogStyle } from 'CustomTheme';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
import { BlockFormControlLabel, ValidatedTextField } from 'components'; import { BlockFormControlLabel, ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils'; import { numberValue, updateValue } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
type CustomEntitiesDialogProps = { import { DeviceValueType, DeviceValueUOM_s } from './types';
import type { EntityItem } from './types';
interface CustomEntitiesDialogProps {
open: boolean; open: boolean;
creating: boolean; creating: boolean;
onClose: () => void; onClose: () => void;
onSave: (ei: EntityItem) => void; onSave: (ei: EntityItem) => void;
selectedItem: EntityItem; selectedItem: EntityItem;
validator: Schema; validator: Schema;
}; }
const CustomEntitiesDialog = ({ const CustomEntitiesDialog = ({
open, open,
@@ -80,8 +79,8 @@ const CustomEntitiesDialog = ({
editItem.type_id = parseInt(editItem.type_id, 16); editItem.type_id = parseInt(editItem.type_id, 16);
} }
onSave(editItem); onSave(editItem);
} catch (errors: any) { } catch (error) {
setFieldErrors(errors); 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 CancelIcon from '@mui/icons-material/Cancel';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from '@mui/icons-material/Search';
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { import {
Button,
Typography,
Box, Box,
MenuItem, Button,
Dialog, Dialog,
DialogActions, DialogActions,
DialogContent, DialogContent,
DialogTitle, DialogTitle,
Grid,
InputAdornment,
Link,
MenuItem,
TextField,
ToggleButton, ToggleButton,
ToggleButtonGroup, ToggleButtonGroup,
Grid, Typography
TextField,
Link,
InputAdornment
} from '@mui/material'; } 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 SettingsCustomizationDialog from './CustomizationDialog';
import EntityMaskToggle from './EntityMaskToggle'; import EntityMaskToggle from './EntityMaskToggle';
import OptionIcon from './OptionIcon'; import OptionIcon from './OptionIcon';
import * as EMSESP from './api';
import { DeviceEntityMask } from './types'; import { DeviceEntityMask } from './types';
import type { DeviceShort, DeviceEntity } from './types'; import type { DeviceEntity, DeviceShort } 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';
export const APIURL = window.location.origin + '/api/'; export const APIURL = window.location.origin + '/api/';
@@ -63,22 +63,27 @@ const Customization: FC = () => {
// fetch devices first // fetch devices first
const { data: devices } = useRequest(EMSESP.readDevices); const { data: devices } = useRequest(EMSESP.readDevices);
// const { state } = useLocation(); const [selectedDevice, setSelectedDevice] = useState<number>(Number(useLocation().state) || -1);
const [selectedDevice, setSelectedDevice] = useState<number>(useLocation().state || -1);
const [selectedDeviceName, setSelectedDeviceName] = useState<string>(''); const [selectedDeviceName, setSelectedDeviceName] = useState<string>('');
const { send: resetCustomizations } = useRequest(EMSESP.resetCustomizations(), { const { send: resetCustomizations } = useRequest(EMSESP.resetCustomizations(), {
immediate: false immediate: false
}); });
const { send: writeCustomizationEntities } = useRequest((data) => EMSESP.writeCustomizationEntities(data), { const { send: writeCustomizationEntities } = useRequest(
immediate: false (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(
initialData: [], (data: number) => EMSESP.readDeviceEntities(data),
immediate: false {
}); initialData: [],
immediate: false
}
);
const setOriginalSettings = (data: DeviceEntity[]) => { 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 }))); 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); setRestartNeeded(false);
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [devices, selectedDevice]); }, [devices, selectedDevice]);
const restart = async () => { const restart = async () => {
await restartCommand().catch((error) => { await restartCommand().catch((error: Error) => {
toast.error(error.message); toast.error(error.message);
}); });
setRestarting(true); setRestarting(true);
}; };
function formatValue(value: any) { function formatValue(value: unknown) {
if (typeof value === 'number') { if (typeof value === 'number') {
return new Intl.NumberFormat().format(value); return new Intl.NumberFormat().format(value);
} else if (value === undefined) { } else if (value === undefined) {
@@ -213,7 +217,7 @@ const Customization: FC = () => {
} else if (typeof value === 'boolean') { } else if (typeof value === 'boolean') {
return value ? 'true' : 'false'; return value ? 'true' : 'false';
} }
return value; return value as string;
} }
const formatName = (de: DeviceEntity, withShortname: boolean) => const formatName = (de: DeviceEntity, withShortname: boolean) =>
@@ -273,7 +277,7 @@ const Customization: FC = () => {
await resetCustomizations(); await resetCustomizations();
toast.info(LL.CUSTOMIZATIONS_RESTART()); toast.info(LL.CUSTOMIZATIONS_RESTART());
} catch (error) { } catch (error) {
toast.error(error.message); toast.error((error as Error).message);
} finally { } finally {
setConfirmReset(false); setConfirmReset(false);
} }
@@ -326,7 +330,7 @@ const Customization: FC = () => {
return; 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') { if (error.message === 'Reboot required') {
setRestartNeeded(true); setRestartNeeded(true);
} else { } else {
@@ -402,7 +406,7 @@ const Customization: FC = () => {
size="small" size="small"
color="secondary" color="secondary"
value={getMaskString(selectedFilters)} value={getMaskString(selectedFilters)}
onChange={(event, mask) => { onChange={(event, mask: string[]) => {
setSelectedFilters(getMaskNumber(mask)); setSelectedFilters(getMaskNumber(mask));
}} }}
> >
@@ -456,7 +460,7 @@ const Customization: FC = () => {
</Grid> </Grid>
</Grid> </Grid>
<Table data={{ nodes: shown_data }} theme={entities_theme} layout={{ custom: true }}> <Table data={{ nodes: shown_data }} theme={entities_theme} layout={{ custom: true }}>
{(tableList: any) => ( {(tableList: DeviceEntity[]) => (
<> <>
<Header> <Header>
<HeaderRow> <HeaderRow>

View File

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

View File

@@ -1,21 +1,22 @@
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd'; import type { FC } from 'react';
import { AiOutlineControl, AiOutlineGateway, AiOutlineAlert } from 'react-icons/ai'; import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/ai';
import { CgSmartHomeBoiler } from 'react-icons/cg'; import { CgSmartHomeBoiler } from 'react-icons/cg';
import { FaSolarPanel } from 'react-icons/fa'; import { FaSolarPanel } from 'react-icons/fa';
import { GiHeatHaze, GiTap } from 'react-icons/gi'; 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 { TiFlowSwitch } from 'react-icons/ti';
import { VscVmConnect } from 'react-icons/vsc'; 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 { interface DeviceIconProps {
type_id: number; type_id: number;
} }
const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => { const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
switch (type_id) { switch (type_id as DeviceType) {
case DeviceType.TEMPERATURESENSOR: case DeviceType.TEMPERATURESENSOR:
case DeviceType.ANALOGSENSOR: case DeviceType.ANALOGSENSOR:
return <MdOutlineSensors />; 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 CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
import EditIcon from '@mui/icons-material/Edit'; import EditIcon from '@mui/icons-material/Edit';
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined'; 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 StarIcon from '@mui/icons-material/Star';
import StarBorderOutlinedIcon from '@mui/icons-material/StarBorderOutlined'; import StarBorderOutlinedIcon from '@mui/icons-material/StarBorderOutlined';
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined'; import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
import { import {
Box,
Button, Button,
Dialog, Dialog,
DialogTitle,
DialogContent,
DialogActions, DialogActions,
DialogContent,
DialogTitle,
Grid,
IconButton, IconButton,
List, List,
ListItem, ListItem,
ListItemText, ListItemText,
Box,
Grid,
Typography Typography
} from '@mui/material'; } from '@mui/material';
import { useRowSelect } from '@table-library/react-table-library/select'; import { useRowSelect } from '@table-library/react-table-library/select';
import { useSort, SortToggleType } from '@table-library/react-table-library/sort'; import { SortToggleType, useSort } from '@table-library/react-table-library/sort';
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'; import { useTheme } from '@table-library/react-table-library/theme';
import { useRequest } from 'alova'; import type { Action, State } from '@table-library/react-table-library/types/common';
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 { dialogStyle } from 'CustomTheme'; 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 { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; 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 Devices: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext); 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: { initialData: {
data: [] data: []
}, },
immediate: false immediate: false
}); });
const { loading: submitting, send: writeDeviceValue } = useRequest((data) => EMSESP.writeDeviceValue(data), { const { loading: submitting, send: writeDeviceValue } = useRequest(
immediate: false (data: { id: number; c: string; v: unknown }) => EMSESP.writeDeviceValue(data),
}); {
immediate: false
}
);
useLayoutEffect(() => { useLayoutEffect(() => {
function updateSize() { 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) { if (state.sortKey === sortKey && state.reverse) {
return <KeyboardArrowDownOutlinedIcon />; return <KeyboardArrowDownOutlinedIcon />;
} }
@@ -235,13 +236,14 @@ const Devices: FC = () => {
sortToggleType: SortToggleType.AlternateWithReset, sortToggleType: SortToggleType.AlternateWithReset,
sortFns: { sortFns: {
NAME: (array) => array.sort((a, b) => a.id.toString().slice(2).localeCompare(b.id.toString().slice(2))), 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())) VALUE: (array) => array.sort((a, b) => a.v.toString().localeCompare(b.v.toString()))
} }
} }
); );
async function onSelectChange(action: any, state: any) { async function onSelectChange(action: Action, state: State) {
setSelectedDevice(state.id); setSelectedDevice(state.id as number);
if (action.type === 'ADD_BY_ID_EXCLUSIVELY') { if (action.type === 'ADD_BY_ID_EXCLUSIVELY') {
await readDeviceData(state.id); await readDeviceData(state.id);
} }
@@ -259,8 +261,8 @@ const Devices: FC = () => {
}; };
const escFunction = useCallback( const escFunction = useCallback(
(event: any) => { (event: KeyboardEvent) => {
if (event.keyCode === 27) { if (event.key === 'Escape') {
if (device_select) { if (device_select) {
device_select.fns.onRemoveAll(); device_select.fns.onRemoveAll();
} }
@@ -290,7 +292,7 @@ const Devices: FC = () => {
} }
}; };
const escapeCsvCell = (cell: any) => { const escapeCsvCell = (cell: string) => {
if (cell == null) { if (cell == null) {
return ''; return '';
} }
@@ -336,9 +338,13 @@ const Devices: FC = () => {
: deviceData.data; : deviceData.data;
const csvData = data.reduce( const csvData = data.reduce(
(csvString: any, rowItem: any) => (csvString: string, rowItem: DeviceValue) =>
csvString + columns.map(({ accessor }: any) => escapeCsvCell(accessor(rowItem))).join(';') + '\r\n', csvString +
columns.map(({ name }: any) => escapeCsvCell(name)).join(';') + '\r\n' 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' }); const csvFile = new Blob([csvData], { type: 'text/csv;charset:utf-8' });
@@ -363,7 +369,7 @@ const Devices: FC = () => {
.then(() => { .then(() => {
toast.success(LL.WRITE_CMD_SENT()); toast.success(LL.WRITE_CMD_SENT());
}) })
.catch((error) => { .catch((error: Error) => {
toast.error(error.message); toast.error(error.message);
}) })
.finally(async () => { .finally(async () => {
@@ -428,7 +434,7 @@ const Devices: FC = () => {
{coreData.connected && ( {coreData.connected && (
<Table data={{ nodes: coreData.devices }} select={device_select} theme={device_theme} layout={{ custom: true }}> <Table data={{ nodes: coreData.devices }} select={device_select} theme={device_theme} layout={{ custom: true }}>
{(tableList: any) => ( {(tableList: Device[]) => (
<> <>
<Header> <Header>
<HeaderRow> <HeaderRow>
@@ -556,7 +562,7 @@ const Devices: FC = () => {
sort={dv_sort} sort={dv_sort}
layout={{ custom: true, fixedHeader: true }} layout={{ custom: true, fixedHeader: true }}
> >
{(tableList: any) => ( {(tableList: DeviceValue[]) => (
<> <>
<Header> <Header>
<HeaderRow> <HeaderRow>

View File

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

View File

@@ -1,12 +1,13 @@
import { ToggleButton, ToggleButtonGroup } from '@mui/material'; import { ToggleButton, ToggleButtonGroup } from '@mui/material';
import OptionIcon from './OptionIcon'; import OptionIcon from './OptionIcon';
import { DeviceEntityMask } from './types'; import { DeviceEntityMask } from './types';
import type { DeviceEntity } from './types'; import type { DeviceEntity } from './types';
type EntityMaskToggleProps = { interface EntityMaskToggleProps {
onUpdate: (de: DeviceEntity) => void; onUpdate: (de: DeviceEntity) => void;
de: DeviceEntity; de: DeviceEntity;
}; }
const EntityMaskToggle = ({ onUpdate, de }: EntityMaskToggleProps) => { const EntityMaskToggle = ({ onUpdate, de }: EntityMaskToggleProps) => {
const getMaskNumber = (newMask: string[]) => { const getMaskNumber = (newMask: string[]) => {
@@ -42,7 +43,7 @@ const EntityMaskToggle = ({ onUpdate, de }: EntityMaskToggleProps) => {
size="small" size="small"
color="secondary" color="secondary"
value={getMaskString(de.m)} value={getMaskString(de.m)}
onChange={(event, mask) => { onChange={(event, mask: string[]) => {
de.m = getMaskNumber(mask); de.m = getMaskNumber(mask);
if (de.n === '' && de.m & DeviceEntityMask.DV_READONLY) { if (de.n === '' && de.m & DeviceEntityMask.DV_READONLY) {
de.m = de.m | DeviceEntityMask.DV_WEB_EXCLUDE; 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 CommentIcon from '@mui/icons-material/CommentTwoTone';
import DownloadIcon from '@mui/icons-material/GetApp'; import DownloadIcon from '@mui/icons-material/GetApp';
import GitHubIcon from '@mui/icons-material/GitHub'; import GitHubIcon from '@mui/icons-material/GitHub';
import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone'; import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone';
import { import {
Avatar,
Box, Box,
Button,
Link,
List, List,
ListItem, ListItem,
ListItemAvatar, ListItemAvatar,
ListItemText,
Link,
Typography,
Button,
ListItemButton, ListItemButton,
Avatar ListItemText,
Typography
} from '@mui/material'; } from '@mui/material';
import * as EMSESP from 'project/api';
import { useRequest } from 'alova'; import { useRequest } from 'alova';
import { toast } from 'react-toastify';
import type { FC } from 'react';
import { SectionContent, useLayoutTitle } from 'components'; import { SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from 'project/api';
import type { APIcall } from './types';
const Help: FC = () => { const Help: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle(LL.HELP_OF('')); 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 immediate: false
}); });
@@ -36,6 +40,7 @@ const Help: FC = () => {
type: 'text/plain' 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.download = 'emsesp_' + event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt';
anchor.click(); anchor.click();
URL.revokeObjectURL(anchor.href); URL.revokeObjectURL(anchor.href);
@@ -43,7 +48,7 @@ const Help: FC = () => {
}); });
const callAPI = async (device: string, entity: string) => { 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); toast.error(error.message);
}); });
}; };

View File

@@ -1,18 +1,16 @@
import type { FC } from 'react';
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined'; import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined'; import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined'; import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined';
import StarIcon from '@mui/icons-material/Star'; import StarIcon from '@mui/icons-material/Star';
import StarOutlineIcon from '@mui/icons-material/StarOutline'; import StarOutlineIcon from '@mui/icons-material/StarOutline';
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined'; import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined'; import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';
import type { SvgIconProps } from '@mui/material'; import type { SvgIconProps } from '@mui/material';
import type { FC } from 'react';
type OptionType = 'deleted' | 'readonly' | 'web_exclude' | 'api_mqtt_exclude' | 'favorite'; 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 AddIcon from '@mui/icons-material/Add';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import CircleIcon from '@mui/icons-material/Circle'; import CircleIcon from '@mui/icons-material/Circle';
import WarningIcon from '@mui/icons-material/Warning'; 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 { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme'; import { useTheme } from '@table-library/react-table-library/theme';
// eslint-disable-next-line import/named
import { updateState, useRequest } from 'alova'; import { updateState, useRequest } from 'alova';
import { useState, useEffect, useCallback } from 'react'; import { BlockNavigation, ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
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 { useI18nContext } from 'i18n/i18n-react'; 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 Scheduler: FC = () => {
const { LL, locale } = useI18nContext(); const { LL, locale } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0); const [numChanges, setNumChanges] = useState<number>(0);
@@ -40,7 +39,9 @@ const Scheduler: FC = () => {
force: true 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) { function hasScheduleChanged(si: ScheduleItem) {
return ( return (
@@ -126,8 +127,8 @@ const Scheduler: FC = () => {
.then(() => { .then(() => {
toast.success(LL.SCHEDULE_UPDATED()); toast.success(LL.SCHEDULE_UPDATED());
}) })
.catch((err) => { .catch((error: Error) => {
toast.error(err.message); toast.error(error.message);
}) })
.finally(async () => { .finally(async () => {
await fetchSchedule(); await fetchSchedule();
@@ -154,11 +155,13 @@ const Scheduler: FC = () => {
const onDialogSave = (updatedItem: ScheduleItem) => { const onDialogSave = (updatedItem: ScheduleItem) => {
setDialogOpen(false); setDialogOpen(false);
updateState('schedule', (data) => { updateState('schedule', (data: ScheduleItem[]) => {
const new_data = creating const new_data = creating
? [...data.filter((si) => creating || si.o_id !== updatedItem.o_id), updatedItem] ? [...data.filter((si) => creating || si.o_id !== updatedItem.o_id), updatedItem]
: data.map((si) => (si.id === updatedItem.id ? { ...si, ...updatedItem } : si)); : data.map((si) => (si.id === updatedItem.id ? { ...si, ...updatedItem } : si));
setNumChanges(new_data.filter((si) => hasScheduleChanged(si)).length); setNumChanges(new_data.filter((si) => hasScheduleChanged(si)).length);
return new_data; return new_data;
}); });
}; };
@@ -202,7 +205,7 @@ const Scheduler: FC = () => {
theme={schedule_theme} theme={schedule_theme}
layout={{ custom: true }} layout={{ custom: true }}
> >
{(tableList: any) => ( {(tableList: ScheduleItem[]) => (
<> <>
<Header> <Header>
<HeaderRow> <HeaderRow>

View File

@@ -1,8 +1,9 @@
import { useEffect, useState } from 'react';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import DoneIcon from '@mui/icons-material/Done'; import DoneIcon from '@mui/icons-material/Done';
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
import { import {
Box, Box,
Button, Button,
@@ -17,22 +18,19 @@ import {
ToggleButtonGroup, ToggleButtonGroup,
Typography Typography
} from '@mui/material'; } 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 { dialogStyle } from 'CustomTheme';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
import { BlockFormControlLabel, ValidatedTextField } from 'components'; import { BlockFormControlLabel, ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { updateValue } from 'utils'; import { updateValue } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
type SchedulerDialogProps = { import { ScheduleFlag } from './types';
import type { ScheduleItem } from './types';
interface SchedulerDialogProps {
open: boolean; open: boolean;
creating: boolean; creating: boolean;
onClose: () => void; onClose: () => void;
@@ -40,7 +38,7 @@ type SchedulerDialogProps = {
selectedItem: ScheduleItem; selectedItem: ScheduleItem;
validator: Schema; validator: Schema;
dow: string[]; dow: string[];
}; }
const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, validator, dow }: SchedulerDialogProps) => { const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, validator, dow }: SchedulerDialogProps) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
@@ -65,8 +63,8 @@ const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, valida
setFieldErrors(undefined); setFieldErrors(undefined);
await validate(validator, editItem); await validate(validator, editItem);
onSave(editItem); onSave(editItem);
} catch (errors: any) { } catch (error) {
setFieldErrors(errors); setFieldErrors(error as ValidateFieldsError);
} }
}; };
@@ -132,7 +130,7 @@ const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, valida
size="small" size="small"
color="secondary" color="secondary"
value={getFlagString(editItem.flags)} value={getFlagString(editItem.flags)}
onChange={(event, flag) => { onChange={(_event, flag: string[]) => {
setEditItem({ ...editItem, flags: getFlagNumber(flag) & 127 }); 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 AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined'; import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined'; import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined'; import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
import { Button, Typography, Box } from '@mui/material'; import { Box, Button, Typography } 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 { 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 { useTheme } from '@table-library/react-table-library/theme';
import type { State } from '@table-library/react-table-library/types/common';
import { useRequest } from 'alova'; 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 { ButtonRow, SectionContent, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import * 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 Sensors: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
@@ -44,11 +44,14 @@ const Sensors: FC = () => {
} }
}); });
const { send: writeTemperatureSensor } = useRequest((data) => EMSESP.writeTemperatureSensor(data), { const { send: writeTemperatureSensor } = useRequest(
immediate: false (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 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) { if (state.sortKey === sortKey && state.reverse) {
return <KeyboardArrowDownOutlinedIcon />; return <KeyboardArrowDownOutlinedIcon />;
} }
@@ -138,6 +141,7 @@ const Sensors: FC = () => {
sortToggleType: SortToggleType.AlternateWithReset, sortToggleType: SortToggleType.AlternateWithReset,
sortFns: { sortFns: {
GPIO: (array) => array.sort((a, b) => a.g - b.g), 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)), NAME: (array) => array.sort((a, b) => a.n.localeCompare(b.n)),
TYPE: (array) => array.sort((a, b) => a.t - b.t), TYPE: (array) => array.sort((a, b) => a.t - b.t),
VALUE: (array) => array.sort((a, b) => a.v - b.v) VALUE: (array) => array.sort((a, b) => a.v - b.v)
@@ -156,6 +160,7 @@ const Sensors: FC = () => {
}, },
sortToggleType: SortToggleType.AlternateWithReset, sortToggleType: SortToggleType.AlternateWithReset,
sortFns: { 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)), NAME: (array) => array.sort((a, b) => a.n.localeCompare(b.n)),
VALUE: (array) => array.sort((a, b) => a.t - b.t) VALUE: (array) => array.sort((a, b) => a.t - b.t)
} }
@@ -189,10 +194,13 @@ const Sensors: FC = () => {
return formatted; return formatted;
}; };
function formatValue(value: any, uom: number) { function formatValue(value: unknown, uom: DeviceValueUOM) {
if (value === undefined) { if (value === undefined) {
return ''; return '';
} }
if (typeof value !== 'number') {
return value as string;
}
switch (uom) { switch (uom) {
case DeviceValueUOM.HOURS: case DeviceValueUOM.HOURS:
return value ? formatDurationMin(value * 60) : LL.NUM_HOURS({ num: 0 }); return value ? formatDurationMin(value * 60) : LL.NUM_HOURS({ num: 0 });
@@ -201,10 +209,7 @@ const Sensors: FC = () => {
case DeviceValueUOM.SECONDS: case DeviceValueUOM.SECONDS:
return LL.NUM_SECONDS({ num: value }); return LL.NUM_SECONDS({ num: value });
case DeviceValueUOM.NONE: case DeviceValueUOM.NONE:
if (typeof value === 'number') { return new Intl.NumberFormat().format(value);
return new Intl.NumberFormat().format(value);
}
return value;
case DeviceValueUOM.DEGREES: case DeviceValueUOM.DEGREES:
case DeviceValueUOM.DEGREES_R: case DeviceValueUOM.DEGREES_R:
case DeviceValueUOM.FAHRENHEIT: case DeviceValueUOM.FAHRENHEIT:
@@ -300,7 +305,7 @@ const Sensors: FC = () => {
const RenderTemperatureSensors = () => ( const RenderTemperatureSensors = () => (
<Table data={{ nodes: sensorData.ts }} theme={temperature_theme} sort={temperature_sort} layout={{ custom: true }}> <Table data={{ nodes: sensorData.ts }} theme={temperature_theme} sort={temperature_sort} layout={{ custom: true }}>
{(tableList: any) => ( {(tableList: TemperatureSensor[]) => (
<> <>
<Header> <Header>
<HeaderRow> <HeaderRow>
@@ -341,7 +346,7 @@ const Sensors: FC = () => {
const RenderAnalogSensors = () => ( const RenderAnalogSensors = () => (
<Table data={{ nodes: sensorData.as }} theme={analog_theme} sort={analog_sort} layout={{ custom: true }}> <Table data={{ nodes: sensorData.as }} theme={analog_theme} sort={analog_sort} layout={{ custom: true }}>
{(tableList: any) => ( {(tableList: AnalogSensor[]) => (
<> <>
<Header> <Header>
<HeaderRow> <HeaderRow>

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