mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 15:59:52 +03:00
new linting, make sure code is type safe
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -63,3 +63,4 @@ bw-output/
|
|||||||
|
|
||||||
# standalone executable for testing
|
# standalone executable for testing
|
||||||
emsesp
|
emsesp
|
||||||
|
interface/tsconfig.tsbuildinfo
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -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
|
||||||
}
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
node_modules/
|
|
||||||
build/
|
|
||||||
dist/
|
|
||||||
.yarn/
|
|
||||||
|
|
||||||
.prettierrc
|
|
||||||
.eslintrc*
|
|
||||||
env.d.ts
|
|
||||||
progmem-generator.js
|
|
||||||
unpack.ts
|
|
||||||
vite.config.ts
|
|
||||||
package.json
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
33
interface/eslint.config.js
Normal file
33
interface/eslint.config.js
Normal 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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 = ' ';
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) =>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) =>
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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'>;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) =>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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} />;
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 = () => {
|
|||||||
) (
|
) (
|
||||||
<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>
|
||||||
) (
|
) (
|
||||||
<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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
(data: { id: number; entity_ids: string[] }) => EMSESP.writeCustomizationEntities(data),
|
||||||
|
{
|
||||||
immediate: false
|
immediate: false
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const { send: readDeviceEntities, onSuccess: onSuccess } = useRequest((data) => EMSESP.readDeviceEntities(data), {
|
const { send: readDeviceEntities, onSuccess: onSuccess } = useRequest(
|
||||||
|
(data: number) => EMSESP.readDeviceEntities(data),
|
||||||
|
{
|
||||||
initialData: [],
|
initialData: [],
|
||||||
immediate: false
|
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>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 />;
|
||||||
|
|||||||
@@ -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(
|
||||||
|
(data: { id: number; c: string; v: unknown }) => EMSESP.writeDeviceValue(data),
|
||||||
|
{
|
||||||
immediate: false
|
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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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(
|
||||||
|
(data: WriteTemperatureSensor) => EMSESP.writeTemperatureSensor(data),
|
||||||
|
{
|
||||||
immediate: false
|
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
Reference in New Issue
Block a user