mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 00:09:51 +03:00
Merge branch 'dev11' into dev
This commit is contained in:
21
.vscode/settings.json
vendored
21
.vscode/settings.json
vendored
@@ -3,9 +3,28 @@
|
|||||||
"**/.yarn": true,
|
"**/.yarn": true,
|
||||||
"**/.pnp.*": true
|
"**/.pnp.*": true
|
||||||
},
|
},
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll": true
|
||||||
|
// "source.organizeImports": true
|
||||||
|
},
|
||||||
"eslint.nodePath": "interface/.yarn/sdks",
|
"eslint.nodePath": "interface/.yarn/sdks",
|
||||||
"eslint.workingDirectories": ["interface"],
|
"eslint.workingDirectories": ["interface"],
|
||||||
"prettier.prettierPath": "interface/.yarn/sdks/prettier/index.js",
|
"prettier.prettierPath": "interface/.yarn/sdks/prettier/index.js",
|
||||||
"typescript.tsdk": "interface/.yarn/sdks/typescript/lib",
|
"typescript.tsdk": "interface/.yarn/sdks/typescript/lib",
|
||||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||||
|
"files.associations": {
|
||||||
|
"*.tsx": "typescriptreact",
|
||||||
|
"*.tcc": "cpp",
|
||||||
|
"optional": "cpp",
|
||||||
|
"istream": "cpp",
|
||||||
|
"ostream": "cpp",
|
||||||
|
"ratio": "cpp",
|
||||||
|
"system_error": "cpp",
|
||||||
|
"array": "cpp",
|
||||||
|
"functional": "cpp",
|
||||||
|
"regex": "cpp",
|
||||||
|
"tuple": "cpp",
|
||||||
|
"type_traits": "cpp",
|
||||||
|
"utility": "cpp"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Added pool data to telegrams 0x494 & 0x495 [#102](https://github.com/emsesp/EMS-ESP32/issues/102)
|
- Added pool data to telegrams 0x494 & 0x495 [#102](https://github.com/emsesp/EMS-ESP32/issues/102)
|
||||||
- Add RC300 second summermode telegram [#108](https://github.com/emsesp/EMS-ESP32/issues/108)
|
- Add RC300 second summermode telegram [#108](https://github.com/emsesp/EMS-ESP32/issues/108)
|
||||||
- Add support for the RC25 thermostat [#106](https://github.com/emsesp/EMS-ESP32/issues/106)
|
- Add support for the RC25 thermostat [#106](https://github.com/emsesp/EMS-ESP32/issues/106)
|
||||||
- Add new command 'entities' for a device, e.g. http://ems-esp/api/boiler/entities to show the shortname, description and HA Entity name (if HA enabled) [#116](https://github.com/emsesp/EMS-ESP32/issues/116)
|
- Add new command 'entities' for a device, e.g. <http://ems-esp/api/boiler/entities> to show the shortname, description and HA Entity name (if HA enabled) [#116](https://github.com/emsesp/EMS-ESP32/issues/116)
|
||||||
- Support for Junkers program and remote (fb10/fb110) temperature
|
- Support for Junkers program and remote (fb10/fb110) temperature
|
||||||
- Home Assistant `state_class` attribute for Wh, kWh, W and KW [#129](https://github.com/emsesp/EMS-ESP32/issues/129)
|
- Home Assistant `state_class` attribute for Wh, kWh, W and KW [#129](https://github.com/emsesp/EMS-ESP32/issues/129)
|
||||||
- Add current room influence for RC300 [#136](https://github.com/emsesp/EMS-ESP32/issues/136)
|
- Add current room influence for RC300 [#136](https://github.com/emsesp/EMS-ESP32/issues/136)
|
||||||
@@ -434,4 +434,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- some names of mqtt-tags like in v2.2.1
|
- some names of mqtt-tags like in v2.2.1
|
||||||
- new ESP32 partition side to allow for smoother OTA and fallback
|
- new ESP32 partition side to allow for smoother OTA and fallback
|
||||||
- Network Gateway IP is optional (#682)emsesp/EMS-ESP
|
- Network Gateway IP is optional (#682)emsesp/EMS-ESP
|
||||||
- moved to a new GitHub repo https://github.com/emsesp/EMS-ESP32
|
- moved to a new GitHub repo <https://github.com/emsesp/EMS-ESP32>
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
|
|
||||||
## **IMPORTANT! BREAKING CHANGES**
|
## **IMPORTANT! BREAKING CHANGES**
|
||||||
|
|
||||||
|
There are breaking changes in 3.6.0. Please read carefully before applying the update.
|
||||||
|
|
||||||
|
- The sensors have been renamed. `dallassensor` is now `temperaturesensor` in MQTT and `ts` in the Customizations file. Also `analogs` is now `analogsensor` in MQTT and `as` in the Customizations file. If you have customizations, make backup first using the Download option and rename the JSON arrays to `as` and `ts` respectively. Also removed any MQTT topics that start with `dallassensor` using something like MQTTExplorer.
|
||||||
|
- The format of the Custom Entities has changed, so you will need to manually re-create them.
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
- Workaround for better Domoticz MQTT intergration? [#904](https://github.com/emsesp/EMS-ESP32/issues/904)
|
- Workaround for better Domoticz MQTT intergration? [#904](https://github.com/emsesp/EMS-ESP32/issues/904)
|
||||||
@@ -29,3 +34,5 @@
|
|||||||
- Improved HA Discovery so previous configs no longer need to be removed when starting [#1077](https://github.com/emsesp/EMS-ESP32/pull/1077) (thanks @pswid!)
|
- Improved HA Discovery so previous configs no longer need to be removed when starting [#1077](https://github.com/emsesp/EMS-ESP32/pull/1077) (thanks @pswid!)
|
||||||
- Enlarge UART-Stack to 2,5k
|
- Enlarge UART-Stack to 2,5k
|
||||||
- Retry timeout for Mqtt-QOS1/2 10seconds
|
- Retry timeout for Mqtt-QOS1/2 10seconds
|
||||||
|
- Optimize WebUI rendering when using Dialog Boxes [#1116](https://github.com/emsesp/EMS-ESP32/issues/1116)
|
||||||
|
- Optimize Web libraries to reduce bundle size (3.6.x) [#1112](https://github.com/emsesp/EMS-ESP32/issues/1112)
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
|
.yarn/
|
||||||
|
|
||||||
.prettierrc
|
.prettierrc
|
||||||
.eslintrc*
|
.eslintrc*
|
||||||
.yarn/
|
|
||||||
env.d.ts
|
env.d.ts
|
||||||
progmem-generator.js
|
progmem-generator.js
|
||||||
vite.config.ts
|
unpack.ts
|
||||||
|
vite.config.ts
|
||||||
|
package.json
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"tsconfigRootDir": ".",
|
"tsconfigRootDir": ".",
|
||||||
"project": ["tsconfig.json"]
|
"project": ["tsconfig.json"]
|
||||||
},
|
},
|
||||||
"plugins": ["react", "@typescript-eslint"],
|
"plugins": ["react", "@typescript-eslint", "autofix", "react-hooks"],
|
||||||
"settings": {
|
"settings": {
|
||||||
"import/resolver": {
|
"import/resolver": {
|
||||||
"typescript": {
|
"typescript": {
|
||||||
@@ -36,7 +36,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"react-hooks/exhaustive-deps": "off",
|
|
||||||
"object-shorthand": "error",
|
"object-shorthand": "error",
|
||||||
"no-console": "warn",
|
"no-console": "warn",
|
||||||
"@typescript-eslint/consistent-type-definitions": ["off", "type"],
|
"@typescript-eslint/consistent-type-definitions": ["off", "type"],
|
||||||
@@ -51,6 +50,44 @@
|
|||||||
"@typescript-eslint/restrict-plus-operands": "off",
|
"@typescript-eslint/restrict-plus-operands": "off",
|
||||||
"@typescript-eslint/no-unused-expressions": "off",
|
"@typescript-eslint/no-unused-expressions": "off",
|
||||||
"@typescript-eslint/no-implied-eval": "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": [
|
"@typescript-eslint/ban-types": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,49 +16,44 @@
|
|||||||
"standalone": "npm-run-all -p dev typesafe-i18n mock-api",
|
"standalone": "npm-run-all -p dev typesafe-i18n mock-api",
|
||||||
"typesafe-i18n": "typesafe-i18n",
|
"typesafe-i18n": "typesafe-i18n",
|
||||||
"format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'",
|
"format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'",
|
||||||
"lint": "eslint . --cache --max-warnings=0"
|
"lint": "eslint . --cache --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.10.6",
|
"@emotion/react": "^11.11.0",
|
||||||
"@emotion/styled": "^11.10.6",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@msgpack/msgpack": "^3.0.0-beta2",
|
|
||||||
"@mui/icons-material": "^5.11.16",
|
"@mui/icons-material": "^5.11.16",
|
||||||
"@mui/material": "^5.11.16",
|
"@mui/material": "^5.12.3",
|
||||||
"@remix-run/router": "^1.5.0",
|
"@table-library/react-table-library": "4.1.4",
|
||||||
"@table-library/react-table-library": "4.1.0",
|
|
||||||
"@types/lodash-es": "^4.17.7",
|
"@types/lodash-es": "^4.17.7",
|
||||||
"@types/node": "^18.15.11",
|
"@types/node": "^20.1.0",
|
||||||
"@types/react": "^18.0.33",
|
"@types/react": "^18.2.6",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^18.2.4",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@yarnpkg/pnpify": "^4.0.0-rc.42",
|
|
||||||
"async-validator": "^4.2.5",
|
"async-validator": "^4.2.5",
|
||||||
"axios": "^1.3.5",
|
"axios": "^1.4.0",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mime-types": "^2.1.35",
|
|
||||||
"react": "latest",
|
"react": "latest",
|
||||||
"react-dom": "latest",
|
"react-dom": "latest",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
"react-icons": "^4.8.0",
|
"react-icons": "^4.8.0",
|
||||||
"react-router-dom": "^6.10.0",
|
"react-router-dom": "^6.11.1",
|
||||||
"react-toastify": "^9.1.2",
|
"react-toastify": "^9.1.2",
|
||||||
"sockette": "^2.0.6",
|
"sockette": "^2.0.6",
|
||||||
"typesafe-i18n": "^5.24.3",
|
"typesafe-i18n": "^5.24.3",
|
||||||
"typescript": "^5.0.3"
|
"typescript": "^5.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mime-types": "^2",
|
"@typescript-eslint/eslint-plugin": "^5.59.2",
|
||||||
"@types/styled-components": "^5",
|
"@typescript-eslint/parser": "^5.59.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
"@vitejs/plugin-react-swc": "^3.3.1",
|
||||||
"@typescript-eslint/parser": "^5.57.1",
|
"eslint": "^8.40.0",
|
||||||
"@vitejs/plugin-react-swc": "^3.2.0",
|
|
||||||
"eslint": "^8.37.0",
|
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"eslint-import-resolver-typescript": "^3.5.5",
|
"eslint-import-resolver-typescript": "^3.5.5",
|
||||||
|
"eslint-plugin-autofix": "^1.1.0",
|
||||||
"eslint-plugin-import": "^2.27.5",
|
"eslint-plugin-import": "^2.27.5",
|
||||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
@@ -66,13 +61,12 @@
|
|||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^2.8.7",
|
"prettier": "^2.8.8",
|
||||||
"rollup-plugin-visualizer": "^5.9.0",
|
"rollup-plugin-visualizer": "^5.9.0",
|
||||||
"terser": "^5.16.8",
|
"terser": "^5.17.2",
|
||||||
"vite": "^4.2.1",
|
"vite": "^4.3.5",
|
||||||
"vite-plugin-minify": "^1.5.2",
|
"vite-plugin-svgr": "^3.2.0",
|
||||||
"vite-plugin-svgr": "^2.4.0",
|
"vite-tsconfig-paths": "^4.2.0"
|
||||||
"vite-tsconfig-paths": "^4.0.8"
|
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@3.4.1"
|
"packageManager": "yarn@3.4.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const { resolve, relative, sep } = require('path');
|
|
||||||
const { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream } = require('fs');
|
const { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream } = require('fs');
|
||||||
|
const { resolve, relative, sep } = require('path');
|
||||||
var zlib = require('zlib');
|
var zlib = require('zlib');
|
||||||
var mime = require('mime-types');
|
var mime = require('mime-types');
|
||||||
|
|
||||||
@@ -18,9 +18,9 @@ function getFilesSync(dir, files = []) {
|
|||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
function coherseToBuffer(input) {
|
// function coherseToBuffer(input) {
|
||||||
return Buffer.isBuffer(input) ? input : Buffer.from(input);
|
// return Buffer.isBuffer(input) ? input : Buffer.from(input);
|
||||||
}
|
// }
|
||||||
|
|
||||||
function cleanAndOpen(path) {
|
function cleanAndOpen(path) {
|
||||||
if (existsSync(path)) {
|
if (existsSync(path)) {
|
||||||
@@ -87,9 +87,8 @@ export default function ProgmemGenerator({ outputPath = './WWWData.h', bytesPerL
|
|||||||
// });
|
// });
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateWWWClass = () => {
|
const generateWWWClass = () =>
|
||||||
// eslint-disable-next-line max-len
|
`typedef std::function<void(const String& uri, const String& contentType, const uint8_t * content, size_t len)> RouteRegistrationHandler;
|
||||||
return `typedef std::function<void(const String& uri, const String& contentType, const uint8_t * content, size_t len)> RouteRegistrationHandler;
|
|
||||||
|
|
||||||
class WWWData {
|
class WWWData {
|
||||||
${indent}public:
|
${indent}public:
|
||||||
@@ -100,8 +99,6 @@ ${fileInfo
|
|||||||
${indent.repeat(2)}}
|
${indent.repeat(2)}}
|
||||||
};
|
};
|
||||||
`;
|
`;
|
||||||
};
|
|
||||||
|
|
||||||
const writeWWWClass = () => {
|
const writeWWWClass = () => {
|
||||||
writeStream.write(generateWWWClass());
|
writeStream.write(generateWWWClass());
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
/*
|
/*
|
||||||
* Just supporting latin due to size constrains on the esp chip
|
* Uses font-size 400 (normal) only and Latin (plus extra unicode chars) to keep flash memory to a minimun
|
||||||
*
|
* View fonts on https://fonts.google.com/
|
||||||
* The framework only makes use of 400 (regular) weight fonts.
|
* Download woff2 using e.g. https://fonts.googleapis.com/css2?family=Lato or https://fonts.googleapis.com/css2?family=Roboto
|
||||||
*
|
|
||||||
* If using medium (500), light or strong typography variants you will need to add additional fonts.
|
|
||||||
*/
|
*/
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/re.woff2) format('woff2');
|
/* src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxK.woff2) format('woff2'); */
|
||||||
|
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/re.off2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0104-0107, U+0118-0119, U+011E-011F, U+0130-0131, U+0141-0144, U+0152-0153, U+015A-015B,
|
unicode-range: U+0000-00FF, U+0104-0107, U+0118-0119, U+011E-011F, U+0130-0131, U+0141-0144, U+0152-0153, U+015A-015B,
|
||||||
U+015E-015F, U+0179-017C, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
|
U+015E-015F, U+0179-017C, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
|
||||||
U+2212, U+2215, U+FEFF, U+FFFD;
|
U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
|||||||
Binary file not shown.
@@ -1,12 +1,13 @@
|
|||||||
import { FC, useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { ToastContainer, Slide } from 'react-toastify';
|
import { ToastContainer, Slide } from 'react-toastify';
|
||||||
|
|
||||||
import 'react-toastify/dist/ReactToastify.min.css';
|
import 'react-toastify/dist/ReactToastify.min.css';
|
||||||
|
|
||||||
import CustomTheme from 'CustomTheme';
|
|
||||||
import AppRouting from 'AppRouting';
|
|
||||||
|
|
||||||
import { localStorageDetector } from 'typesafe-i18n/detectors';
|
import { localStorageDetector } from 'typesafe-i18n/detectors';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
import AppRouting from 'AppRouting';
|
||||||
|
import CustomTheme from 'CustomTheme';
|
||||||
|
|
||||||
import TypesafeI18n from 'i18n/i18n-react';
|
import 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';
|
||||||
@@ -17,7 +18,7 @@ const App: FC = () => {
|
|||||||
const [wasLoaded, setWasLoaded] = useState(false);
|
const [wasLoaded, setWasLoaded] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadLocaleAsync(detectedLocale).then(() => setWasLoaded(true));
|
void loadLocaleAsync(detectedLocale).then(() => setWasLoaded(true));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!wasLoaded) return null;
|
if (!wasLoaded) return null;
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { FC, useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
|
|
||||||
import { Route, Routes, Navigate, useLocation } from 'react-router-dom';
|
import { Route, Routes, Navigate, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import AuthenticatedRouting from 'AuthenticatedRouting';
|
||||||
|
import SignIn from 'SignIn';
|
||||||
import { Authentication, AuthenticationContext } from 'contexts/authentication';
|
|
||||||
import { RequireAuthenticated, RequireUnauthenticated } from 'components';
|
import { RequireAuthenticated, RequireUnauthenticated } from 'components';
|
||||||
|
|
||||||
import SignIn from 'SignIn';
|
import { Authentication, AuthenticationContext } from 'contexts/authentication';
|
||||||
import AuthenticatedRouting from 'AuthenticatedRouting';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
interface SecurityRedirectProps {
|
interface SecurityRedirectProps {
|
||||||
message: string;
|
message: string;
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import { FC, useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { Navigate, Routes, Route, useNavigate, useLocation } from 'react-router-dom';
|
import { Navigate, Routes, Route, useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { AxiosError } from 'axios';
|
import Dashboard from './project/Dashboard';
|
||||||
|
import Help from './project/Help';
|
||||||
|
import Settings from './project/Settings';
|
||||||
|
import type { AxiosError } from 'axios';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import * as AuthenticationApi from 'api/authentication';
|
import * as AuthenticationApi from 'api/authentication';
|
||||||
import { AXIOS } from 'api/endpoints';
|
import { AXIOS } from 'api/endpoints';
|
||||||
import { Layout, RequireAdmin } from 'components';
|
import { Layout, RequireAdmin } from 'components';
|
||||||
|
|
||||||
import Dashboard from './project/Dashboard';
|
|
||||||
import Settings from './project/Settings';
|
|
||||||
import Help from './project/Help';
|
|
||||||
|
|
||||||
import NetworkConnection from 'framework/network/NetworkConnection';
|
|
||||||
import AccessPoint from 'framework/ap/AccessPoint';
|
import AccessPoint from 'framework/ap/AccessPoint';
|
||||||
import NetworkTime from 'framework/ntp/NetworkTime';
|
|
||||||
import Mqtt from 'framework/mqtt/Mqtt';
|
import Mqtt from 'framework/mqtt/Mqtt';
|
||||||
import System from 'framework/system/System';
|
import NetworkConnection from 'framework/network/NetworkConnection';
|
||||||
|
import NetworkTime from 'framework/ntp/NetworkTime';
|
||||||
import Security from 'framework/security/Security';
|
import Security from 'framework/security/Security';
|
||||||
|
import System from 'framework/system/System';
|
||||||
|
|
||||||
const AuthenticatedRouting: FC = () => {
|
const AuthenticatedRouting: FC = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
import { CssBaseline } from '@mui/material';
|
import { CssBaseline } from '@mui/material';
|
||||||
import { createTheme, responsiveFontSizes, ThemeProvider } from '@mui/material/styles';
|
|
||||||
import { blueGrey, blue } from '@mui/material/colors';
|
import { blueGrey, blue } from '@mui/material/colors';
|
||||||
|
import { createTheme, responsiveFontSizes, ThemeProvider } from '@mui/material/styles';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { RequiredChildrenProps } from 'utils';
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
|
|
||||||
const theme = responsiveFontSizes(
|
const theme = responsiveFontSizes(
|
||||||
createTheme({
|
createTheme({
|
||||||
|
|||||||
@@ -1,31 +1,30 @@
|
|||||||
import { FC, useContext, useState } from 'react';
|
|
||||||
import { ValidateFieldsError } from 'async-validator';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import { Box, Fab, Paper, Typography, Button } from '@mui/material';
|
|
||||||
import ForwardIcon from '@mui/icons-material/Forward';
|
import ForwardIcon from '@mui/icons-material/Forward';
|
||||||
|
import { Box, Fab, Paper, Typography, Button } from '@mui/material';
|
||||||
|
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 { FC } from 'react';
|
||||||
|
import type { SignInRequest } from 'types';
|
||||||
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 { ValidatedTextField } from 'components';
|
||||||
import { AuthenticationContext } from 'contexts/authentication';
|
import { AuthenticationContext } from 'contexts/authentication';
|
||||||
|
|
||||||
import { extractErrorMessage, onEnterCallback, updateValue } from 'utils';
|
|
||||||
import { SignInRequest } from 'types';
|
|
||||||
import { ValidatedTextField } from 'components';
|
|
||||||
import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators';
|
|
||||||
|
|
||||||
import { I18nContext } from 'i18n/i18n-react';
|
|
||||||
import type { Locales } from 'i18n/i18n-types';
|
|
||||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
|
||||||
|
|
||||||
import { ReactComponent as NLflag } from 'i18n/NL.svg';
|
|
||||||
import { ReactComponent as DEflag } from 'i18n/DE.svg';
|
import { ReactComponent as DEflag } from 'i18n/DE.svg';
|
||||||
import { ReactComponent as GBflag } from 'i18n/GB.svg';
|
|
||||||
import { ReactComponent as SVflag } from 'i18n/SV.svg';
|
|
||||||
import { ReactComponent as PLflag } from 'i18n/PL.svg';
|
|
||||||
import { ReactComponent as NOflag } from 'i18n/NO.svg';
|
|
||||||
import { ReactComponent as FRflag } from 'i18n/FR.svg';
|
import { ReactComponent as FRflag } from 'i18n/FR.svg';
|
||||||
|
import { ReactComponent as GBflag } from 'i18n/GB.svg';
|
||||||
|
import { ReactComponent as NLflag } from 'i18n/NL.svg';
|
||||||
|
import { ReactComponent as NOflag } from 'i18n/NO.svg';
|
||||||
|
import { ReactComponent as PLflag } from 'i18n/PL.svg';
|
||||||
|
import { ReactComponent as SVflag } from 'i18n/SV.svg';
|
||||||
import { ReactComponent as TRflag } from 'i18n/TR.svg';
|
import { ReactComponent as TRflag } from 'i18n/TR.svg';
|
||||||
|
import { I18nContext } from 'i18n/i18n-react';
|
||||||
|
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||||
|
import { extractErrorMessage, onEnterCallback, updateValue } from 'utils';
|
||||||
|
import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators';
|
||||||
|
|
||||||
const SignIn: FC = () => {
|
const SignIn: FC = () => {
|
||||||
const authenticationContext = useContext(AuthenticationContext);
|
const authenticationContext = useContext(AuthenticationContext);
|
||||||
@@ -64,7 +63,7 @@ const SignIn: FC = () => {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
|
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
|
||||||
signIn();
|
await signIn();
|
||||||
} catch (errors: any) {
|
} catch (errors: any) {
|
||||||
setFieldErrors(errors);
|
setFieldErrors(errors);
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
|
||||||
|
|
||||||
import { APSettings, APStatus } from 'types';
|
|
||||||
import { AXIOS } from './endpoints';
|
import { AXIOS } from './endpoints';
|
||||||
|
import type { AxiosPromise } from 'axios';
|
||||||
|
|
||||||
|
import type { APSettings, APStatus } from 'types';
|
||||||
|
|
||||||
export function readAPStatus(): AxiosPromise<APStatus> {
|
export function readAPStatus(): AxiosPromise<APStatus> {
|
||||||
return AXIOS.get('/apStatus');
|
return AXIOS.get('/apStatus');
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
|
||||||
import * as H from 'history';
|
|
||||||
import jwtDecode from 'jwt-decode';
|
import jwtDecode from 'jwt-decode';
|
||||||
import { Path } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Me, SignInRequest, SignInResponse } from 'types';
|
|
||||||
|
|
||||||
import { ACCESS_TOKEN, AXIOS } from './endpoints';
|
import { ACCESS_TOKEN, AXIOS } from './endpoints';
|
||||||
|
import type { AxiosPromise } from 'axios';
|
||||||
|
import type * as H from 'history';
|
||||||
|
import type { Path } from 'react-router-dom';
|
||||||
|
|
||||||
|
import type { Me, SignInRequest, SignInResponse } from 'types';
|
||||||
|
|
||||||
export const SIGN_IN_PATHNAME = 'loginPathname';
|
export const SIGN_IN_PATHNAME = 'loginPathname';
|
||||||
export const SIGN_IN_SEARCH = 'loginSearch';
|
export const SIGN_IN_SEARCH = 'loginSearch';
|
||||||
@@ -45,7 +44,7 @@ export function fetchLoginRedirect(): Partial<Path> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const clearAccessToken = () => localStorage.removeItem(ACCESS_TOKEN);
|
export const clearAccessToken = () => localStorage.removeItem(ACCESS_TOKEN);
|
||||||
export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken) as Me;
|
export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken);
|
||||||
|
|
||||||
export function addAccessTokenParameter(url: string) {
|
export function addAccessTokenParameter(url: string) {
|
||||||
const accessToken = getStorage().getItem(ACCESS_TOKEN);
|
const accessToken = getStorage().getItem(ACCESS_TOKEN);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import axios, { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios';
|
import axios from 'axios';
|
||||||
|
import { unpack } from './unpack';
|
||||||
|
|
||||||
import { decode } from '@msgpack/msgpack';
|
import type { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios';
|
||||||
|
|
||||||
export const WS_BASE_URL = '/ws/';
|
export const WS_BASE_URL = '/ws/';
|
||||||
export const API_BASE_URL = '/rest/';
|
export const API_BASE_URL = '/rest/';
|
||||||
@@ -72,11 +73,8 @@ export const AXIOS_BIN = axios.create({
|
|||||||
return JSON.stringify(data);
|
return JSON.stringify(data);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
transformResponse: [
|
// transformResponse: [(data) => decode(data)]
|
||||||
(data) => {
|
transformResponse: [(data) => unpack(data)] // new using msgpackr
|
||||||
return decode(data);
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface FileUploadConfig {
|
export interface FileUploadConfig {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
|
||||||
|
|
||||||
import { Features } from 'types';
|
|
||||||
|
|
||||||
import { AXIOS } from './endpoints';
|
import { AXIOS } from './endpoints';
|
||||||
|
import type { AxiosPromise } from 'axios';
|
||||||
|
|
||||||
|
import type { Features } from 'types';
|
||||||
|
|
||||||
export function readFeatures(): AxiosPromise<Features> {
|
export function readFeatures(): AxiosPromise<Features> {
|
||||||
return AXIOS.get('/features');
|
return AXIOS.get('/features');
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
|
||||||
import { MqttSettings, MqttStatus } from 'types';
|
|
||||||
|
|
||||||
import { AXIOS } from './endpoints';
|
import { AXIOS } from './endpoints';
|
||||||
|
import type { AxiosPromise } from 'axios';
|
||||||
|
import type { MqttSettings, MqttStatus } from 'types';
|
||||||
|
|
||||||
export function readMqttStatus(): AxiosPromise<MqttStatus> {
|
export function readMqttStatus(): AxiosPromise<MqttStatus> {
|
||||||
return AXIOS.get('/mqttStatus');
|
return AXIOS.get('/mqttStatus');
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
|
||||||
|
|
||||||
import { WiFiNetworkList, NetworkSettings, NetworkStatus } from 'types';
|
|
||||||
|
|
||||||
import { AXIOS } from './endpoints';
|
import { AXIOS } from './endpoints';
|
||||||
|
import type { AxiosPromise } from 'axios';
|
||||||
|
|
||||||
|
import type { WiFiNetworkList, NetworkSettings, NetworkStatus } from 'types';
|
||||||
|
|
||||||
export function readNetworkStatus(): AxiosPromise<NetworkStatus> {
|
export function readNetworkStatus(): AxiosPromise<NetworkStatus> {
|
||||||
return AXIOS.get('/networkStatus');
|
return AXIOS.get('/networkStatus');
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
|
||||||
import { NTPSettings, NTPStatus, Time } from 'types';
|
|
||||||
|
|
||||||
import { AXIOS } from './endpoints';
|
import { AXIOS } from './endpoints';
|
||||||
|
import type { AxiosPromise } from 'axios';
|
||||||
|
import type { NTPSettings, NTPStatus, Time } from 'types';
|
||||||
|
|
||||||
export function readNTPStatus(): AxiosPromise<NTPStatus> {
|
export function readNTPStatus(): AxiosPromise<NTPStatus> {
|
||||||
return AXIOS.get('/ntpStatus');
|
return AXIOS.get('/ntpStatus');
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
|
||||||
|
|
||||||
import { SecuritySettings, Token } from 'types';
|
|
||||||
|
|
||||||
import { AXIOS } from './endpoints';
|
import { AXIOS } from './endpoints';
|
||||||
|
import type { AxiosPromise } from 'axios';
|
||||||
|
|
||||||
|
import type { SecuritySettings, Token } from 'types';
|
||||||
|
|
||||||
export function readSecuritySettings(): AxiosPromise<SecuritySettings> {
|
export function readSecuritySettings(): AxiosPromise<SecuritySettings> {
|
||||||
return AXIOS.get('/securitySettings');
|
return AXIOS.get('/securitySettings');
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
import { AXIOS, AXIOS_BIN, startUploadFile } from './endpoints';
|
||||||
|
import type { FileUploadConfig } from './endpoints';
|
||||||
|
import type { AxiosPromise } from 'axios';
|
||||||
|
|
||||||
import { OTASettings, SystemStatus, LogSettings, LogEntries } from 'types';
|
import type { OTASettings, SystemStatus, LogSettings, LogEntries } from 'types';
|
||||||
|
|
||||||
import { AXIOS, AXIOS_BIN, FileUploadConfig, startUploadFile } from './endpoints';
|
|
||||||
|
|
||||||
export function readSystemStatus(timeout?: number): AxiosPromise<SystemStatus> {
|
export function readSystemStatus(timeout?: number): AxiosPromise<SystemStatus> {
|
||||||
return AXIOS.get('/systemStatus', { timeout });
|
return AXIOS.get('/systemStatus', { timeout });
|
||||||
|
|||||||
1134
interface/src/api/unpack.ts
Normal file
1134
interface/src/api/unpack.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,25 @@
|
|||||||
import { FC } from 'react';
|
import { Box } from '@mui/material';
|
||||||
import { Box, 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 }) => (
|
||||||
return (
|
<Box
|
||||||
<Box
|
sx={{
|
||||||
sx={{
|
'& button, & a, & .MuiCard-root': {
|
||||||
'& button, & a, & .MuiCard-root': {
|
mt: 2,
|
||||||
mt: 2,
|
mx: 0.6,
|
||||||
mx: 0.6,
|
'&:last-child': {
|
||||||
'&:last-child': {
|
mr: 0
|
||||||
mr: 0
|
},
|
||||||
},
|
'&:first-of-type': {
|
||||||
'&:first-of-type': {
|
ml: 0
|
||||||
ml: 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
{...rest}
|
}}
|
||||||
>
|
{...rest}
|
||||||
{children}
|
>
|
||||||
</Box>
|
{children}
|
||||||
);
|
</Box>
|
||||||
};
|
);
|
||||||
|
|
||||||
export default ButtonRow;
|
export default ButtonRow;
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
import { Box, BoxProps, SvgIconProps, Theme, Typography, useTheme } from '@mui/material';
|
|
||||||
|
|
||||||
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
|
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
|
||||||
|
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 ErrorIcon from '@mui/icons-material/Error';
|
import { Box, Typography, useTheme } from '@mui/material';
|
||||||
|
import type { BoxProps, SvgIconProps, Theme } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
type MessageBoxLevel = 'warning' | 'success' | 'info' | 'error';
|
type MessageBoxLevel = 'warning' | 'success' | 'info' | 'error';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
import { Paper, Divider } from '@mui/material';
|
import { Paper, Divider } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { RequiredChildrenProps } from 'utils';
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
|
|
||||||
interface SectionContentProps extends RequiredChildrenProps {
|
interface SectionContentProps extends RequiredChildrenProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { FC } from 'react';
|
import { FormControlLabel } from '@mui/material';
|
||||||
import { FormControlLabel, 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,10 +1,11 @@
|
|||||||
import { FC, useState } from 'react';
|
|
||||||
|
|
||||||
import { IconButton, InputAdornment } from '@mui/material';
|
|
||||||
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 { useState } from 'react';
|
||||||
|
|
||||||
import ValidatedTextField, { ValidatedTextFieldProps } from './ValidatedTextField';
|
import ValidatedTextField 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,7 @@
|
|||||||
import { FC } from 'react';
|
import { FormHelperText, TextField } from '@mui/material';
|
||||||
import { ValidateFieldsError } from 'async-validator';
|
import type { TextFieldProps } from '@mui/material';
|
||||||
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
import { FormHelperText, TextField, TextFieldProps } from '@mui/material';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
interface ValidatedFieldProps {
|
interface ValidatedFieldProps {
|
||||||
fieldErrors?: ValidateFieldsError;
|
fieldErrors?: ValidateFieldsError;
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { FC, useState, useEffect } from 'react';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Box, Toolbar } from '@mui/material';
|
import { Box, Toolbar } from '@mui/material';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
import { PROJECT_NAME } from 'api/env';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { RequiredChildrenProps } from 'utils';
|
|
||||||
|
|
||||||
import LayoutDrawer from './LayoutDrawer';
|
|
||||||
import LayoutAppBar from './LayoutAppBar';
|
import LayoutAppBar from './LayoutAppBar';
|
||||||
|
import LayoutDrawer from './LayoutDrawer';
|
||||||
import { LayoutContext } from './context';
|
import { LayoutContext } from './context';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
export const DRAWER_WIDTH = 240;
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
|
import { PROJECT_NAME } from 'api/env';
|
||||||
|
|
||||||
|
export const DRAWER_WIDTH = 210;
|
||||||
|
|
||||||
const Layout: FC<RequiredChildrenProps> = ({ children }) => {
|
const Layout: FC<RequiredChildrenProps> = ({ children }) => {
|
||||||
const [mobileOpen, setMobileOpen] = useState(false);
|
const [mobileOpen, setMobileOpen] = useState(false);
|
||||||
|
|||||||
@@ -1,46 +1,42 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
import { AppBar, Box, IconButton, Toolbar, Typography } from '@mui/material';
|
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
import MenuIcon from '@mui/icons-material/Menu';
|
||||||
|
import { AppBar, Box, IconButton, Toolbar, Typography } from '@mui/material';
|
||||||
import LayoutAuthMenu from './LayoutAuthMenu';
|
import LayoutAuthMenu from './LayoutAuthMenu';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
export const DRAWER_WIDTH = 240;
|
export const DRAWER_WIDTH = 210;
|
||||||
|
|
||||||
interface LayoutAppBarProps {
|
interface LayoutAppBarProps {
|
||||||
title: string;
|
title: string;
|
||||||
onToggleDrawer: () => void;
|
onToggleDrawer: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LayoutAppBar: FC<LayoutAppBarProps> = ({ title, onToggleDrawer }) => {
|
const LayoutAppBar: FC<LayoutAppBarProps> = ({ title, onToggleDrawer }) => (
|
||||||
return (
|
<AppBar
|
||||||
<AppBar
|
position="fixed"
|
||||||
position="fixed"
|
sx={{
|
||||||
sx={{
|
width: { md: `calc(100% - ${DRAWER_WIDTH}px)` },
|
||||||
width: { md: `calc(100% - ${DRAWER_WIDTH}px)` },
|
ml: { md: `${DRAWER_WIDTH}px` },
|
||||||
ml: { md: `${DRAWER_WIDTH}px` },
|
boxShadow: 'none',
|
||||||
boxShadow: 'none',
|
backgroundColor: '#2e586a'
|
||||||
backgroundColor: '#2e586a'
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<Toolbar>
|
||||||
<Toolbar>
|
<IconButton
|
||||||
<IconButton
|
color="inherit"
|
||||||
color="inherit"
|
aria-label="open drawer"
|
||||||
aria-label="open drawer"
|
edge="start"
|
||||||
edge="start"
|
onClick={onToggleDrawer}
|
||||||
onClick={onToggleDrawer}
|
sx={{ mr: 2, display: { md: 'none' } }}
|
||||||
sx={{ mr: 2, display: { md: 'none' } }}
|
>
|
||||||
>
|
<MenuIcon />
|
||||||
<MenuIcon />
|
</IconButton>
|
||||||
</IconButton>
|
<Typography variant="h6" noWrap component="div">
|
||||||
<Typography variant="h6" noWrap component="div">
|
{title}
|
||||||
{title}
|
</Typography>
|
||||||
</Typography>
|
<Box flexGrow={1} />
|
||||||
<Box flexGrow={1} />
|
<LayoutAuthMenu />
|
||||||
<LayoutAuthMenu />
|
</Toolbar>
|
||||||
</Toolbar>
|
</AppBar>
|
||||||
</AppBar>
|
);
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LayoutAppBar;
|
export default LayoutAppBar;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FC, useState, useContext, ChangeEventHandler } from 'react';
|
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
||||||
|
import PersonIcon from '@mui/icons-material/Person';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
@@ -9,28 +9,26 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
Avatar,
|
Avatar,
|
||||||
styled,
|
styled,
|
||||||
TypographyProps,
|
|
||||||
MenuItem,
|
MenuItem,
|
||||||
TextField
|
TextField
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
import { useState, useContext } from 'react';
|
||||||
|
import type { TypographyProps } from '@mui/material';
|
||||||
|
|
||||||
import PersonIcon from '@mui/icons-material/Person';
|
import type { Locales } from 'i18n/i18n-types';
|
||||||
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
import type { FC, ChangeEventHandler } from 'react';
|
||||||
|
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
|
||||||
import { I18nContext } from 'i18n/i18n-react';
|
|
||||||
import type { Locales } from 'i18n/i18n-types';
|
|
||||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
|
||||||
|
|
||||||
import { ReactComponent as NLflag } from 'i18n/NL.svg';
|
|
||||||
import { ReactComponent as DEflag } from 'i18n/DE.svg';
|
import { ReactComponent as DEflag } from 'i18n/DE.svg';
|
||||||
import { ReactComponent as GBflag } from 'i18n/GB.svg';
|
|
||||||
import { ReactComponent as SVflag } from 'i18n/SV.svg';
|
|
||||||
import { ReactComponent as PLflag } from 'i18n/PL.svg';
|
|
||||||
import { ReactComponent as NOflag } from 'i18n/NO.svg';
|
|
||||||
import { ReactComponent as FRflag } from 'i18n/FR.svg';
|
import { ReactComponent as FRflag } from 'i18n/FR.svg';
|
||||||
|
import { ReactComponent as GBflag } from 'i18n/GB.svg';
|
||||||
|
import { ReactComponent as NLflag } from 'i18n/NL.svg';
|
||||||
|
import { ReactComponent as NOflag } from 'i18n/NO.svg';
|
||||||
|
import { ReactComponent as PLflag } from 'i18n/PL.svg';
|
||||||
|
import { ReactComponent as SVflag } from 'i18n/SV.svg';
|
||||||
import { ReactComponent as TRflag } from 'i18n/TR.svg';
|
import { ReactComponent as TRflag } from 'i18n/TR.svg';
|
||||||
|
import { I18nContext } from 'i18n/i18n-react';
|
||||||
|
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||||
|
|
||||||
const ItemTypography = styled(Typography)<TypographyProps>({
|
const ItemTypography = styled(Typography)<TypographyProps>({
|
||||||
maxWidth: '250px',
|
maxWidth: '250px',
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
|
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 { PROJECT_NAME } from 'api/env';
|
import { PROJECT_NAME } from 'api/env';
|
||||||
|
|
||||||
import LayoutMenu from './LayoutMenu';
|
|
||||||
import { DRAWER_WIDTH } from './Layout';
|
|
||||||
|
|
||||||
const LayoutDrawerLogo = styled('img')(({ theme }) => ({
|
const LayoutDrawerLogo = styled('img')(({ theme }) => ({
|
||||||
[theme.breakpoints.down('sm')]: {
|
[theme.breakpoints.down('sm')]: {
|
||||||
height: 24,
|
height: 24,
|
||||||
marginRight: theme.spacing(2)
|
marginRight: theme.spacing(2)
|
||||||
},
|
},
|
||||||
[theme.breakpoints.up('sm')]: {
|
[theme.breakpoints.up('sm')]: {
|
||||||
height: 36,
|
height: 38,
|
||||||
marginRight: theme.spacing(2)
|
marginRight: theme.spacing(2)
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -29,9 +27,7 @@ const LayoutDrawer: FC<LayoutDrawerProps> = ({ mobileOpen, onClose }) => {
|
|||||||
<Toolbar disableGutters>
|
<Toolbar disableGutters>
|
||||||
<Box display="flex" alignItems="center" px={2}>
|
<Box display="flex" alignItems="center" px={2}>
|
||||||
<LayoutDrawerLogo src="/app/icon.png" alt={PROJECT_NAME} />
|
<LayoutDrawerLogo src="/app/icon.png" alt={PROJECT_NAME} />
|
||||||
<Typography variant="h6" color="textPrimary">
|
<Typography variant="h6">{PROJECT_NAME}</Typography>
|
||||||
{PROJECT_NAME}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Divider absolute />
|
<Divider absolute />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import { FC, useContext } from 'react';
|
|
||||||
|
|
||||||
import { Divider, List } from '@mui/material';
|
|
||||||
|
|
||||||
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
|
|
||||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
|
||||||
import SettingsIcon from '@mui/icons-material/Settings';
|
|
||||||
import LockIcon from '@mui/icons-material/Lock';
|
|
||||||
import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
|
|
||||||
|
|
||||||
import TuneIcon from '@mui/icons-material/Tune';
|
|
||||||
import DashboardIcon from '@mui/icons-material/Dashboard';
|
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||||
|
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||||
import InfoIcon from '@mui/icons-material/Info';
|
import InfoIcon from '@mui/icons-material/Info';
|
||||||
|
import LockIcon from '@mui/icons-material/Lock';
|
||||||
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
|
import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
|
||||||
|
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
|
||||||
|
import TuneIcon from '@mui/icons-material/Tune';
|
||||||
|
import { Divider, List } from '@mui/material';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import LayoutMenuItem from 'components/layout/LayoutMenuItem';
|
import LayoutMenuItem from 'components/layout/LayoutMenuItem';
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { FC } from 'react';
|
import { ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
|
||||||
|
import { grey } from '@mui/material/colors';
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
import type { SvgIconProps } from '@mui/material';
|
||||||
import { ListItem, ListItemButton, ListItemIcon, ListItemText, SvgIconProps } from '@mui/material';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { routeMatches } from 'utils';
|
import { routeMatches } from 'utils';
|
||||||
|
|
||||||
import { grey } from '@mui/material/colors';
|
|
||||||
|
|
||||||
interface LayoutMenuItemProps {
|
interface LayoutMenuItemProps {
|
||||||
icon: React.ComponentType<SvgIconProps>;
|
icon: React.ComponentType<SvgIconProps>;
|
||||||
label: string;
|
label: string;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
import { Box, Paper, Typography } from '@mui/material';
|
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
import { Box, Paper, Typography } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
interface ApplicationErrorProps {
|
interface ApplicationErrorProps {
|
||||||
message?: string;
|
message?: string;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
import { Box, Button, CircularProgress, Typography } from '@mui/material';
|
|
||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
|
import { Box, Button, CircularProgress, Typography } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { MessageBox } from 'components';
|
import { MessageBox } from 'components';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FC } from 'react';
|
import { CircularProgress, Box, Typography } from '@mui/material';
|
||||||
|
import type { Theme } from '@mui/material';
|
||||||
import { CircularProgress, Box, Typography, Theme } from '@mui/material';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
import type { Blocker } from '@remix-run/router';
|
|
||||||
import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
|
import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import type { unstable_Blocker as Blocker } from 'react-router-dom';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { FC, useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { Navigate } from 'react-router-dom';
|
import { Navigate } from 'react-router-dom';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { RequiredChildrenProps } from 'utils';
|
|
||||||
|
|
||||||
const RequireAdmin: FC<RequiredChildrenProps> = ({ children }) => {
|
const RequireAdmin: FC<RequiredChildrenProps> = ({ children }) => {
|
||||||
const authenticatedContext = useContext(AuthenticatedContext);
|
const authenticatedContext = useContext(AuthenticatedContext);
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { FC, useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
import { Navigate, useLocation } from 'react-router-dom';
|
import { Navigate, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import {
|
import type { AuthenticatedContextValue } from 'contexts/authentication/context';
|
||||||
AuthenticatedContext,
|
import type { FC } from 'react';
|
||||||
AuthenticatedContextValue,
|
|
||||||
AuthenticationContext
|
|
||||||
} from 'contexts/authentication/context';
|
|
||||||
import { storeLoginRedirect } from 'api/authentication';
|
|
||||||
|
|
||||||
import { RequiredChildrenProps } from 'utils';
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
|
import { storeLoginRedirect } from 'api/authentication';
|
||||||
|
import { AuthenticatedContext, AuthenticationContext } from 'contexts/authentication/context';
|
||||||
|
|
||||||
const RequireAuthenticated: FC<RequiredChildrenProps> = ({ children }) => {
|
const RequireAuthenticated: FC<RequiredChildrenProps> = ({ children }) => {
|
||||||
const authenticationContext = useContext(AuthenticationContext);
|
const authenticationContext = useContext(AuthenticationContext);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { FC, useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { Navigate } from 'react-router-dom';
|
import { Navigate } from 'react-router-dom';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
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 { RequiredChildrenProps } from 'utils';
|
|
||||||
|
|
||||||
const RequireUnauthenticated: FC<RequiredChildrenProps> = ({ children }) => {
|
const RequireUnauthenticated: FC<RequiredChildrenProps> = ({ children }) => {
|
||||||
const authenticationContext = useContext(AuthenticationContext);
|
const authenticationContext = useContext(AuthenticationContext);
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Tabs, useMediaQuery, useTheme } from '@mui/material';
|
import { Tabs, useMediaQuery, useTheme } from '@mui/material';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { RequiredChildrenProps } from 'utils';
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
|
|
||||||
interface RouterTabsProps extends RequiredChildrenProps {
|
interface RouterTabsProps extends RequiredChildrenProps {
|
||||||
value: string | false;
|
value: string | false;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { FC, Fragment } from 'react';
|
|
||||||
import { useDropzone, DropzoneState } from 'react-dropzone';
|
|
||||||
|
|
||||||
import { AxiosProgressEvent } from 'axios';
|
|
||||||
|
|
||||||
import { Box, Button, LinearProgress, Theme, Typography, useTheme } from '@mui/material';
|
|
||||||
|
|
||||||
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
||||||
|
import { Box, Button, LinearProgress, Typography, useTheme } from '@mui/material';
|
||||||
|
import { Fragment } from 'react';
|
||||||
|
import { useDropzone } from 'react-dropzone';
|
||||||
|
import type { Theme } from '@mui/material';
|
||||||
|
import type { AxiosProgressEvent } from 'axios';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
import type { DropzoneState } from 'react-dropzone';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
import axios from 'axios';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import axios, { AxiosPromise, CancelTokenSource, AxiosProgressEvent } from 'axios';
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { extractErrorMessage } from 'utils';
|
import type { FileUploadConfig } from 'api/endpoints';
|
||||||
import { FileUploadConfig } from 'api/endpoints';
|
import type { AxiosPromise, CancelTokenSource, AxiosProgressEvent } from 'axios';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { extractErrorMessage } from 'utils';
|
||||||
|
|
||||||
interface MediaUploadOptions {
|
interface MediaUploadOptions {
|
||||||
upload: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
upload: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
||||||
@@ -31,11 +32,12 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => {
|
|||||||
resetUploadingStates();
|
resetUploadingStates();
|
||||||
}, [uploadCancelToken]);
|
}, [uploadCancelToken]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(
|
||||||
return () => {
|
() => () => {
|
||||||
uploadCancelToken?.cancel();
|
uploadCancelToken?.cancel();
|
||||||
};
|
},
|
||||||
}, [uploadCancelToken]);
|
[uploadCancelToken]
|
||||||
|
);
|
||||||
|
|
||||||
const uploadFile = async (images: File[]) => {
|
const uploadFile = async (images: File[]) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { FC, useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { AuthenticationContext } from './context';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-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 { RequiredChildrenProps } from 'utils';
|
|
||||||
import { LoadingSpinner } from 'components';
|
import { LoadingSpinner } from 'components';
|
||||||
import { Me } from 'types';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { AuthenticationContext } from './context';
|
|
||||||
|
|
||||||
const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
@@ -57,7 +57,7 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refresh();
|
void refresh();
|
||||||
}, [refresh]);
|
}, [refresh]);
|
||||||
|
|
||||||
if (initialized) {
|
if (initialized) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
import { Me } from 'types';
|
import type { Me } from 'types';
|
||||||
|
|
||||||
export interface AuthenticationContextValue {
|
export interface AuthenticationContextValue {
|
||||||
refresh: () => Promise<void>;
|
refresh: () => Promise<void>;
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { FC, useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import * as FeaturesApi from 'api/features';
|
|
||||||
|
|
||||||
import { extractErrorMessage, RequiredChildrenProps } from 'utils';
|
|
||||||
import { Features } from 'types';
|
|
||||||
import { ApplicationError, LoadingSpinner } from 'components';
|
|
||||||
|
|
||||||
import { FeaturesContext } from '.';
|
import { FeaturesContext } from '.';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import type { Features } from 'types';
|
||||||
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
|
import * as FeaturesApi from 'api/features';
|
||||||
|
import { ApplicationError, LoadingSpinner } from 'components';
|
||||||
|
import { extractErrorMessage } from 'utils';
|
||||||
|
|
||||||
const FeaturesLoader: FC<RequiredChildrenProps> = (props) => {
|
const FeaturesLoader: FC<RequiredChildrenProps> = (props) => {
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
const [errorMessage, setErrorMessage] = useState<string>();
|
||||||
@@ -22,7 +23,7 @@ const FeaturesLoader: FC<RequiredChildrenProps> = (props) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadFeatures();
|
void loadFeatures();
|
||||||
}, [loadFeatures]);
|
}, [loadFeatures]);
|
||||||
|
|
||||||
if (features) {
|
if (features) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
|
|
||||||
import { Features } from 'types';
|
import type { Features } from 'types';
|
||||||
|
|
||||||
export interface FeaturesContextValue {
|
export interface FeaturesContextValue {
|
||||||
features: Features;
|
features: Features;
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { FC, useState } from 'react';
|
|
||||||
import { ValidateFieldsError } from 'async-validator';
|
|
||||||
import { range } from 'lodash-es';
|
|
||||||
|
|
||||||
import { Button, Checkbox, MenuItem } from '@mui/material';
|
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
import { Button, Checkbox, MenuItem } from '@mui/material';
|
||||||
|
import { range } from 'lodash-es';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { createAPSettingsValidator, validate } from 'validators';
|
import type { APSettings } from 'types';
|
||||||
|
import * as APApi from 'api/ap';
|
||||||
import {
|
import {
|
||||||
BlockFormControlLabel,
|
BlockFormControlLabel,
|
||||||
ButtonRow,
|
ButtonRow,
|
||||||
@@ -17,15 +18,14 @@ import {
|
|||||||
BlockNavigation
|
BlockNavigation
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
|
||||||
import { APProvisionMode, APSettings } from 'types';
|
|
||||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
|
||||||
import * as APApi from 'api/ap';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { APProvisionMode } from 'types';
|
||||||
|
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||||
|
|
||||||
export const isAPEnabled = ({ provision_mode }: APSettings) => {
|
import { createAPSettingsValidator, validate } from 'validators';
|
||||||
return provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
|
|
||||||
};
|
export const isAPEnabled = ({ provision_mode }: APSettings) =>
|
||||||
|
provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
|
||||||
|
|
||||||
const APSettingsForm: FC = () => {
|
const APSettingsForm: FC = () => {
|
||||||
const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
|
const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
|
||||||
@@ -49,7 +49,7 @@ const APSettingsForm: FC = () => {
|
|||||||
try {
|
try {
|
||||||
setFieldErrors(undefined);
|
setFieldErrors(undefined);
|
||||||
await validate(createAPSettingsValidator(data), data);
|
await validate(createAPSettingsValidator(data), data);
|
||||||
saveData();
|
await saveData();
|
||||||
} catch (errors: any) {
|
} catch (errors: any) {
|
||||||
setFieldErrors(errors);
|
setFieldErrors(errors);
|
||||||
}
|
}
|
||||||
@@ -174,7 +174,7 @@ const APSettingsForm: FC = () => {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => loadData()}
|
onClick={loadData}
|
||||||
>
|
>
|
||||||
{LL.CANCEL()}
|
{LL.CANCEL()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, Theme, useTheme } from '@mui/material';
|
|
||||||
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
|
|
||||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
|
||||||
import ComputerIcon from '@mui/icons-material/Computer';
|
import ComputerIcon from '@mui/icons-material/Computer';
|
||||||
|
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 { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material';
|
||||||
|
import type { Theme } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import type { APStatus } from 'types';
|
||||||
import * as APApi from 'api/ap';
|
import * as APApi from 'api/ap';
|
||||||
import { APNetworkStatus, APStatus } from 'types';
|
|
||||||
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
||||||
import { useRest } from 'utils';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { APNetworkStatus } from 'types';
|
||||||
|
import { useRest } from 'utils';
|
||||||
|
|
||||||
export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
|
export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { FC, useContext } from 'react';
|
import { Tab } from '@mui/material';
|
||||||
|
import { useContext } from 'react';
|
||||||
import { Navigate, Routes, Route } from 'react-router-dom';
|
import { Navigate, Routes, Route } from 'react-router-dom';
|
||||||
|
|
||||||
import { Tab } from '@mui/material';
|
|
||||||
|
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
|
||||||
import APStatusForm from './APStatusForm';
|
|
||||||
import APSettingsForm from './APSettingsForm';
|
import APSettingsForm from './APSettingsForm';
|
||||||
|
import APStatusForm from './APStatusForm';
|
||||||
|
import type { FC } from 'react';
|
||||||
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components';
|
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components';
|
||||||
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
@@ -36,7 +36,6 @@ const AccessPoint: FC = () => {
|
|||||||
</RequireAdmin>
|
</RequireAdmin>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{/* <Route path="/*" element={<Navigate to="status" />} /> */}
|
|
||||||
<Route path="/*" element={<Navigate replace to="status" />} />
|
<Route path="/*" element={<Navigate replace to="status" />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { FC, useContext } from 'react';
|
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Tab } from '@mui/material';
|
import { Tab } from '@mui/material';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
|
import MqttSettingsForm from './MqttSettingsForm';
|
||||||
|
import MqttStatusForm from './MqttStatusForm';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components';
|
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
|
||||||
import MqttStatusForm from './MqttStatusForm';
|
|
||||||
import MqttSettingsForm from './MqttSettingsForm';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
const Mqtt: FC = () => {
|
const Mqtt: FC = () => {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { FC, useState } from 'react';
|
|
||||||
import { ValidateFieldsError } from 'async-validator';
|
|
||||||
|
|
||||||
import { Button, Checkbox, MenuItem, Grid, Typography, InputAdornment } from '@mui/material';
|
|
||||||
|
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
import { Button, Checkbox, MenuItem, Grid, Typography, InputAdornment, TextField } from '@mui/material';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { createMqttSettingsValidator, validate } from 'validators';
|
import type { MqttSettings } from 'types';
|
||||||
|
import * as MqttApi from 'api/mqtt';
|
||||||
import {
|
import {
|
||||||
BlockFormControlLabel,
|
BlockFormControlLabel,
|
||||||
ButtonRow,
|
ButtonRow,
|
||||||
@@ -16,11 +16,10 @@ import {
|
|||||||
ValidatedTextField,
|
ValidatedTextField,
|
||||||
BlockNavigation
|
BlockNavigation
|
||||||
} from 'components';
|
} from 'components';
|
||||||
import { MqttSettings } from 'types';
|
|
||||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
|
||||||
import * as MqttApi from 'api/mqtt';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||||
|
|
||||||
|
import { createMqttSettingsValidator, validate } from 'validators';
|
||||||
|
|
||||||
const MqttSettingsForm: FC = () => {
|
const MqttSettingsForm: FC = () => {
|
||||||
const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
|
const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
|
||||||
@@ -44,7 +43,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
try {
|
try {
|
||||||
setFieldErrors(undefined);
|
setFieldErrors(undefined);
|
||||||
await validate(createMqttSettingsValidator(data), data);
|
await validate(createMqttSettingsValidator(data), data);
|
||||||
saveData();
|
await saveData();
|
||||||
} catch (errors: any) {
|
} catch (errors: any) {
|
||||||
setFieldErrors(errors);
|
setFieldErrors(errors);
|
||||||
}
|
}
|
||||||
@@ -95,7 +94,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={6}>
|
<Grid item xs={12} sm={6}>
|
||||||
<ValidatedTextField
|
<TextField
|
||||||
name="client_id"
|
name="client_id"
|
||||||
label={LL.ID_OF(LL.CLIENT()) + ' (' + LL.OPTIONAL() + ')'}
|
label={LL.ID_OF(LL.CLIENT()) + ' (' + LL.OPTIONAL() + ')'}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -106,7 +105,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={6}>
|
<Grid item xs={12} sm={6}>
|
||||||
<ValidatedTextField
|
<TextField
|
||||||
name="username"
|
name="username"
|
||||||
label={LL.USERNAME(0)}
|
label={LL.USERNAME(0)}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -144,7 +143,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={6}>
|
<Grid item xs={12} sm={6}>
|
||||||
<ValidatedTextField
|
<TextField
|
||||||
name="mqtt_qos"
|
name="mqtt_qos"
|
||||||
label="QoS"
|
label="QoS"
|
||||||
value={data.mqtt_qos}
|
value={data.mqtt_qos}
|
||||||
@@ -157,7 +156,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
<MenuItem value={0}>0</MenuItem>
|
<MenuItem value={0}>0</MenuItem>
|
||||||
<MenuItem value={1}>1</MenuItem>
|
<MenuItem value={1}>1</MenuItem>
|
||||||
<MenuItem value={2}>2</MenuItem>
|
<MenuItem value={2}>2</MenuItem>
|
||||||
</ValidatedTextField>
|
</TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<BlockFormControlLabel
|
<BlockFormControlLabel
|
||||||
@@ -172,7 +171,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||||
{LL.FORMATTING()}
|
{LL.FORMATTING()}
|
||||||
</Typography>
|
</Typography>
|
||||||
<ValidatedTextField
|
<TextField
|
||||||
name="nested_format"
|
name="nested_format"
|
||||||
label={LL.MQTT_FORMAT()}
|
label={LL.MQTT_FORMAT()}
|
||||||
value={data.nested_format}
|
value={data.nested_format}
|
||||||
@@ -184,7 +183,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
>
|
>
|
||||||
<MenuItem value={1}>{LL.MQTT_NEST_1()}</MenuItem>
|
<MenuItem value={1}>{LL.MQTT_NEST_1()}</MenuItem>
|
||||||
<MenuItem value={2}>{LL.MQTT_NEST_2()}</MenuItem>
|
<MenuItem value={2}>{LL.MQTT_NEST_2()}</MenuItem>
|
||||||
</ValidatedTextField>
|
</TextField>
|
||||||
<BlockFormControlLabel
|
<BlockFormControlLabel
|
||||||
control={<Checkbox name="send_response" checked={data.send_response} onChange={updateFormValue} />}
|
control={<Checkbox name="send_response" checked={data.send_response} onChange={updateFormValue} />}
|
||||||
label={LL.MQTT_RESPONSE()}
|
label={LL.MQTT_RESPONSE()}
|
||||||
@@ -234,7 +233,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
alignItems="flex-start"
|
alignItems="flex-start"
|
||||||
>
|
>
|
||||||
<Grid item xs={12} sm={6} md={4}>
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
<ValidatedTextField
|
<TextField
|
||||||
name="discovery_type"
|
name="discovery_type"
|
||||||
label={LL.MQTT_PUBLISH_TEXT_5()}
|
label={LL.MQTT_PUBLISH_TEXT_5()}
|
||||||
value={data.discovery_type}
|
value={data.discovery_type}
|
||||||
@@ -246,10 +245,10 @@ const MqttSettingsForm: FC = () => {
|
|||||||
>
|
>
|
||||||
<MenuItem value={0}>Home Assistant</MenuItem>
|
<MenuItem value={0}>Home Assistant</MenuItem>
|
||||||
<MenuItem value={1}>Domoticz</MenuItem>
|
<MenuItem value={1}>Domoticz</MenuItem>
|
||||||
</ValidatedTextField>
|
</TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={6} md={4}>
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
<ValidatedTextField
|
<TextField
|
||||||
name="discovery_prefix"
|
name="discovery_prefix"
|
||||||
label={LL.MQTT_PUBLISH_TEXT_4()}
|
label={LL.MQTT_PUBLISH_TEXT_4()}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -260,7 +259,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={6} md={4}>
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
<ValidatedTextField
|
<TextField
|
||||||
name="entity_format"
|
name="entity_format"
|
||||||
label={LL.MQTT_ENTITY_FORMAT()}
|
label={LL.MQTT_ENTITY_FORMAT()}
|
||||||
value={data.entity_format}
|
value={data.entity_format}
|
||||||
@@ -273,7 +272,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
<MenuItem value={0}>{LL.MQTT_ENTITY_FORMAT_0()}</MenuItem>
|
<MenuItem value={0}>{LL.MQTT_ENTITY_FORMAT_0()}</MenuItem>
|
||||||
<MenuItem value={1}>{LL.MQTT_ENTITY_FORMAT_1()}</MenuItem>
|
<MenuItem value={1}>{LL.MQTT_ENTITY_FORMAT_1()}</MenuItem>
|
||||||
<MenuItem value={2}>{LL.MQTT_ENTITY_FORMAT_2()}</MenuItem>
|
<MenuItem value={2}>{LL.MQTT_ENTITY_FORMAT_2()}</MenuItem>
|
||||||
</ValidatedTextField>
|
</TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
@@ -300,8 +299,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={6} md={4}>
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
<ValidatedTextField
|
<TextField
|
||||||
fieldErrors={fieldErrors}
|
|
||||||
name="publish_time_boiler"
|
name="publish_time_boiler"
|
||||||
label={LL.MQTT_INT_BOILER()}
|
label={LL.MQTT_INT_BOILER()}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
@@ -316,8 +314,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={6} md={4}>
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
<ValidatedTextField
|
<TextField
|
||||||
fieldErrors={fieldErrors}
|
|
||||||
name="publish_time_thermostat"
|
name="publish_time_thermostat"
|
||||||
label={LL.MQTT_INT_THERMOSTATS()}
|
label={LL.MQTT_INT_THERMOSTATS()}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
@@ -332,8 +329,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={6} md={4}>
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
<ValidatedTextField
|
<TextField
|
||||||
fieldErrors={fieldErrors}
|
|
||||||
name="publish_time_solar"
|
name="publish_time_solar"
|
||||||
label={LL.MQTT_INT_SOLAR()}
|
label={LL.MQTT_INT_SOLAR()}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
@@ -348,8 +344,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={6} md={4}>
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
<ValidatedTextField
|
<TextField
|
||||||
fieldErrors={fieldErrors}
|
|
||||||
name="publish_time_mixer"
|
name="publish_time_mixer"
|
||||||
label={LL.MQTT_INT_MIXER()}
|
label={LL.MQTT_INT_MIXER()}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
@@ -364,8 +359,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={6} md={4}>
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
<ValidatedTextField
|
<TextField
|
||||||
fieldErrors={fieldErrors}
|
|
||||||
name="publish_time_sensor"
|
name="publish_time_sensor"
|
||||||
label={LL.TEMP_SENSORS()}
|
label={LL.TEMP_SENSORS()}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
@@ -380,8 +374,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={6} md={4}>
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
<ValidatedTextField
|
<TextField
|
||||||
fieldErrors={fieldErrors}
|
|
||||||
name="publish_time_other"
|
name="publish_time_other"
|
||||||
InputProps={{
|
InputProps={{
|
||||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||||
@@ -405,7 +398,7 @@ const MqttSettingsForm: FC = () => {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => loadData()}
|
onClick={loadData}
|
||||||
>
|
>
|
||||||
{LL.CANCEL()}
|
{LL.CANCEL()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { FC } from 'react';
|
import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion';
|
||||||
import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, Theme, useTheme } from '@mui/material';
|
|
||||||
|
|
||||||
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 AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion';
|
import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material';
|
||||||
|
import type { Theme } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
import type { MqttStatus } from 'types';
|
||||||
import { MqttStatus, MqttDisconnectReason } from 'types';
|
|
||||||
import * as MqttApi from 'api/mqtt';
|
import * as MqttApi from 'api/mqtt';
|
||||||
import { useRest } from 'utils';
|
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { MqttDisconnectReason } from 'types';
|
||||||
|
import { useRest } from 'utils';
|
||||||
|
|
||||||
export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: Theme) => {
|
export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: Theme) => {
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
@@ -83,50 +83,48 @@ const MqttStatusForm: FC = () => {
|
|||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderConnectionStatus = () => {
|
const renderConnectionStatus = () => (
|
||||||
return (
|
<>
|
||||||
<>
|
{!data.connected && (
|
||||||
{!data.connected && (
|
<>
|
||||||
<>
|
<ListItem>
|
||||||
<ListItem>
|
<ListItemAvatar>
|
||||||
<ListItemAvatar>
|
<Avatar>
|
||||||
<Avatar>
|
<ReportIcon />
|
||||||
<ReportIcon />
|
</Avatar>
|
||||||
</Avatar>
|
</ListItemAvatar>
|
||||||
</ListItemAvatar>
|
<ListItemText primary={LL.DISCONNECT_REASON()} secondary={disconnectReason(data)} />
|
||||||
<ListItemText primary={LL.DISCONNECT_REASON()} secondary={disconnectReason(data)} />
|
</ListItem>
|
||||||
</ListItem>
|
<Divider variant="inset" component="li" />
|
||||||
<Divider variant="inset" component="li" />
|
</>
|
||||||
</>
|
)}
|
||||||
)}
|
<ListItem>
|
||||||
<ListItem>
|
<ListItemAvatar>
|
||||||
<ListItemAvatar>
|
<Avatar>#</Avatar>
|
||||||
<Avatar>#</Avatar>
|
</ListItemAvatar>
|
||||||
</ListItemAvatar>
|
<ListItemText primary={LL.ID_OF(LL.CLIENT())} secondary={data.client_id} />
|
||||||
<ListItemText primary={LL.ID_OF(LL.CLIENT())} secondary={data.client_id} />
|
</ListItem>
|
||||||
</ListItem>
|
<Divider variant="inset" component="li" />
|
||||||
<Divider variant="inset" component="li" />
|
<ListItem>
|
||||||
<ListItem>
|
<ListItemAvatar>
|
||||||
<ListItemAvatar>
|
<Avatar sx={{ bgcolor: mqttQueueHighlight(data, theme) }}>
|
||||||
<Avatar sx={{ bgcolor: mqttQueueHighlight(data, theme) }}>
|
<AutoAwesomeMotionIcon />
|
||||||
<AutoAwesomeMotionIcon />
|
</Avatar>
|
||||||
</Avatar>
|
</ListItemAvatar>
|
||||||
</ListItemAvatar>
|
<ListItemText primary={LL.MQTT_QUEUE()} secondary={data.mqtt_queued} />
|
||||||
<ListItemText primary={LL.MQTT_QUEUE()} secondary={data.mqtt_queued} />
|
</ListItem>
|
||||||
</ListItem>
|
<Divider variant="inset" component="li" />
|
||||||
<Divider variant="inset" component="li" />
|
<ListItem>
|
||||||
<ListItem>
|
<ListItemAvatar>
|
||||||
<ListItemAvatar>
|
<Avatar sx={{ bgcolor: mqttPublishHighlight(data, theme) }}>
|
||||||
<Avatar sx={{ bgcolor: mqttPublishHighlight(data, theme) }}>
|
<SpeakerNotesOffIcon />
|
||||||
<SpeakerNotesOffIcon />
|
</Avatar>
|
||||||
</Avatar>
|
</ListItemAvatar>
|
||||||
</ListItemAvatar>
|
<ListItemText primary={LL.ERRORS_OF('MQTT')} secondary={data.mqtt_fails} />
|
||||||
<ListItemText primary={LL.ERRORS_OF('MQTT')} secondary={data.mqtt_fails} />
|
</ListItem>
|
||||||
</ListItem>
|
<Divider variant="inset" component="li" />
|
||||||
<Divider variant="inset" component="li" />
|
</>
|
||||||
</>
|
);
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { FC, useCallback, useContext, useState } from 'react';
|
|
||||||
import { Navigate, Routes, Route, useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Tab } from '@mui/material';
|
import { Tab } from '@mui/material';
|
||||||
|
import { useCallback, useContext, useState } from 'react';
|
||||||
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components';
|
import { Navigate, Routes, Route, useNavigate } from 'react-router-dom';
|
||||||
import { WiFiNetwork } from 'types';
|
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
|
||||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
|
||||||
import NetworkStatusForm from './NetworkStatusForm';
|
|
||||||
import WiFiNetworkScanner from './WiFiNetworkScanner';
|
|
||||||
import NetworkSettingsForm from './NetworkSettingsForm';
|
import NetworkSettingsForm from './NetworkSettingsForm';
|
||||||
|
import NetworkStatusForm from './NetworkStatusForm';
|
||||||
|
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||||
|
import WiFiNetworkScanner from './WiFiNetworkScanner';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import type { WiFiNetwork } from 'types';
|
||||||
|
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components';
|
||||||
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
const NetworkConnection: FC = () => {
|
const NetworkConnection: FC = () => {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { FC, useContext, useEffect, useState } from 'react';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import { toast } from 'react-toastify';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
import LockIcon from '@mui/icons-material/Lock';
|
||||||
|
import LockOpenIcon from '@mui/icons-material/LockOpen';
|
||||||
|
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Button,
|
Button,
|
||||||
@@ -12,16 +15,19 @@ import {
|
|||||||
ListItemSecondaryAction,
|
ListItemSecondaryAction,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
Typography,
|
Typography,
|
||||||
InputAdornment
|
InputAdornment,
|
||||||
|
TextField
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
import { useContext, useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import RestartMonitor from '../system/RestartMonitor';
|
||||||
|
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||||
|
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
|
||||||
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import LockOpenIcon from '@mui/icons-material/LockOpen';
|
import type { NetworkSettings } from 'types';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import * as NetworkApi from 'api/network';
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
|
||||||
import LockIcon from '@mui/icons-material/Lock';
|
|
||||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BlockFormControlLabel,
|
BlockFormControlLabel,
|
||||||
ButtonRow,
|
ButtonRow,
|
||||||
@@ -32,20 +38,13 @@ import {
|
|||||||
MessageBox,
|
MessageBox,
|
||||||
BlockNavigation
|
BlockNavigation
|
||||||
} from 'components';
|
} from 'components';
|
||||||
import { NetworkSettings } from 'types';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import * as NetworkApi from 'api/network';
|
|
||||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
|
||||||
import * as EMSESP from 'project/api';
|
import * as EMSESP from 'project/api';
|
||||||
|
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||||
|
|
||||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
|
||||||
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
|
|
||||||
import { ValidateFieldsError } from 'async-validator';
|
|
||||||
import { validate } from 'validators';
|
import { validate } from 'validators';
|
||||||
import { createNetworkSettingsValidator } from 'validators/network';
|
import { createNetworkSettingsValidator } from 'validators/network';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
|
||||||
import RestartMonitor from '../system/RestartMonitor';
|
|
||||||
|
|
||||||
const WiFiSettingsForm: FC = () => {
|
const WiFiSettingsForm: FC = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
@@ -106,7 +105,7 @@ const WiFiSettingsForm: FC = () => {
|
|||||||
try {
|
try {
|
||||||
setFieldErrors(undefined);
|
setFieldErrors(undefined);
|
||||||
await validate(createNetworkSettingsValidator(data), data);
|
await validate(createNetworkSettingsValidator(data), data);
|
||||||
saveData();
|
await saveData();
|
||||||
} catch (errors: any) {
|
} catch (errors: any) {
|
||||||
setFieldErrors(errors);
|
setFieldErrors(errors);
|
||||||
}
|
}
|
||||||
@@ -167,7 +166,6 @@ const WiFiSettingsForm: FC = () => {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors}
|
||||||
name="tx_power"
|
name="tx_power"
|
||||||
@@ -182,21 +180,17 @@ const WiFiSettingsForm: FC = () => {
|
|||||||
type="number"
|
type="number"
|
||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<BlockFormControlLabel
|
<BlockFormControlLabel
|
||||||
control={<Checkbox name="nosleep" checked={data.nosleep} onChange={updateFormValue} />}
|
control={<Checkbox name="nosleep" checked={data.nosleep} onChange={updateFormValue} />}
|
||||||
label={LL.NETWORK_DISABLE_SLEEP()}
|
label={LL.NETWORK_DISABLE_SLEEP()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<BlockFormControlLabel
|
<BlockFormControlLabel
|
||||||
control={<Checkbox name="bandwidth20" checked={data.bandwidth20} onChange={updateFormValue} />}
|
control={<Checkbox name="bandwidth20" checked={data.bandwidth20} onChange={updateFormValue} />}
|
||||||
label={LL.NETWORK_LOW_BAND()}
|
label={LL.NETWORK_LOW_BAND()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||||
{LL.GENERAL_OPTIONS()}
|
{LL.GENERAL_OPTIONS()}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors}
|
||||||
name="hostname"
|
name="hostname"
|
||||||
@@ -207,19 +201,16 @@ const WiFiSettingsForm: FC = () => {
|
|||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<BlockFormControlLabel
|
<BlockFormControlLabel
|
||||||
control={<Checkbox name="enableMDNS" checked={data.enableMDNS} onChange={updateFormValue} />}
|
control={<Checkbox name="enableMDNS" checked={data.enableMDNS} onChange={updateFormValue} />}
|
||||||
label={LL.NETWORK_USE_DNS()}
|
label={LL.NETWORK_USE_DNS()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<BlockFormControlLabel
|
<BlockFormControlLabel
|
||||||
control={<Checkbox name="enableCORS" checked={data.enableCORS} onChange={updateFormValue} />}
|
control={<Checkbox name="enableCORS" checked={data.enableCORS} onChange={updateFormValue} />}
|
||||||
label={LL.NETWORK_ENABLE_CORS()}
|
label={LL.NETWORK_ENABLE_CORS()}
|
||||||
/>
|
/>
|
||||||
{data.enableCORS && (
|
{data.enableCORS && (
|
||||||
<ValidatedTextField
|
<TextField
|
||||||
fieldErrors={fieldErrors}
|
|
||||||
name="CORSOrigin"
|
name="CORSOrigin"
|
||||||
label={LL.NETWORK_CORS_ORIGIN()}
|
label={LL.NETWORK_CORS_ORIGIN()}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -229,12 +220,10 @@ const WiFiSettingsForm: FC = () => {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<BlockFormControlLabel
|
<BlockFormControlLabel
|
||||||
control={<Checkbox name="enableIPv6" checked={data.enableIPv6} onChange={updateFormValue} />}
|
control={<Checkbox name="enableIPv6" checked={data.enableIPv6} onChange={updateFormValue} />}
|
||||||
label={LL.NETWORK_ENABLE_IPV6()}
|
label={LL.NETWORK_ENABLE_IPV6()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<BlockFormControlLabel
|
<BlockFormControlLabel
|
||||||
control={<Checkbox name="static_ip_config" checked={data.static_ip_config} onChange={updateFormValue} />}
|
control={<Checkbox name="static_ip_config" checked={data.static_ip_config} onChange={updateFormValue} />}
|
||||||
label={LL.NETWORK_FIXED_IP()}
|
label={LL.NETWORK_FIXED_IP()}
|
||||||
@@ -309,7 +298,7 @@ const WiFiSettingsForm: FC = () => {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => loadData()}
|
onClick={loadData}
|
||||||
>
|
>
|
||||||
{LL.CANCEL()}
|
{LL.CANCEL()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, Theme, useTheme } from '@mui/material';
|
|
||||||
|
|
||||||
import SettingsInputComponentIcon from '@mui/icons-material/SettingsInputComponent';
|
|
||||||
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
|
|
||||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||||
import WifiIcon from '@mui/icons-material/Wifi';
|
|
||||||
import DnsIcon from '@mui/icons-material/Dns';
|
import DnsIcon from '@mui/icons-material/Dns';
|
||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
import RouterIcon from '@mui/icons-material/Router';
|
import RouterIcon from '@mui/icons-material/Router';
|
||||||
|
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
|
||||||
|
import SettingsInputComponentIcon from '@mui/icons-material/SettingsInputComponent';
|
||||||
|
import WifiIcon from '@mui/icons-material/Wifi';
|
||||||
|
import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material';
|
||||||
|
import type { Theme } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
import type { NetworkStatus } from 'types';
|
||||||
import { NetworkConnectionStatus, NetworkStatus } from 'types';
|
|
||||||
import * as NetworkApi from 'api/network';
|
import * as NetworkApi from 'api/network';
|
||||||
import { useRest } from 'utils';
|
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { NetworkConnectionStatus } from 'types';
|
||||||
|
import { useRest } from 'utils';
|
||||||
|
|
||||||
const isConnected = ({ status }: NetworkStatus) =>
|
const isConnected = ({ status }: NetworkStatus) =>
|
||||||
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED ||
|
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED ||
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
import { WiFiNetwork } from 'types';
|
import type { WiFiNetwork } from 'types';
|
||||||
|
|
||||||
export interface WiFiConnectionContextValue {
|
export interface WiFiConnectionContextValue {
|
||||||
selectedNetwork?: WiFiNetwork;
|
selectedNetwork?: WiFiNetwork;
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { useEffect, FC, useState, useCallback, useRef } from 'react';
|
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
|
||||||
|
import { Button } from '@mui/material';
|
||||||
|
import { useEffect, useState, useCallback, useRef } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { Button } from '@mui/material';
|
|
||||||
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
|
|
||||||
|
|
||||||
import * as NetworkApi from 'api/network';
|
|
||||||
import { WiFiNetwork, WiFiNetworkList } from 'types';
|
|
||||||
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
|
||||||
|
|
||||||
import WiFiNetworkSelector from './WiFiNetworkSelector';
|
import WiFiNetworkSelector from './WiFiNetworkSelector';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
import type { WiFiNetwork, WiFiNetworkList } from 'types';
|
||||||
|
import * as NetworkApi from 'api/network';
|
||||||
|
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
@@ -76,7 +75,7 @@ const WiFiNetworkScanner: FC = () => {
|
|||||||
}, [finishedWithError, pollNetworkList, LL]);
|
}, [finishedWithError, pollNetworkList, LL]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
startNetworkScan();
|
void startNetworkScan();
|
||||||
}, [startNetworkScan]);
|
}, [startNetworkScan]);
|
||||||
|
|
||||||
const renderNetworkScanner = () => {
|
const renderNetworkScanner = () => {
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import { FC, useContext } from 'react';
|
|
||||||
|
|
||||||
import { Avatar, Badge, List, ListItem, ListItemAvatar, ListItemIcon, ListItemText } from '@mui/material';
|
|
||||||
|
|
||||||
import LockOpenIcon from '@mui/icons-material/LockOpen';
|
|
||||||
import LockIcon from '@mui/icons-material/Lock';
|
import LockIcon from '@mui/icons-material/Lock';
|
||||||
|
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 } from '@mui/material';
|
||||||
import { MessageBox } from 'components';
|
import { useContext } from 'react';
|
||||||
|
|
||||||
import { WiFiEncryptionType, WiFiNetwork, WiFiNetworkList } from 'types';
|
|
||||||
|
|
||||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
import type { WiFiNetwork, WiFiNetworkList } from 'types';
|
||||||
|
import { MessageBox } from 'components';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { WiFiEncryptionType } from 'types';
|
||||||
|
|
||||||
interface WiFiNetworkSelectorProps {
|
interface WiFiNetworkSelectorProps {
|
||||||
networkList: WiFiNetworkList;
|
networkList: WiFiNetworkList;
|
||||||
@@ -45,24 +43,22 @@ const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => {
|
|||||||
|
|
||||||
const wifiConnectionContext = useContext(WiFiConnectionContext);
|
const wifiConnectionContext = useContext(WiFiConnectionContext);
|
||||||
|
|
||||||
const renderNetwork = (network: WiFiNetwork) => {
|
const renderNetwork = (network: WiFiNetwork) => (
|
||||||
return (
|
<ListItem key={network.bssid} onClick={() => wifiConnectionContext.selectNetwork(network)}>
|
||||||
<ListItem key={network.bssid} button onClick={() => wifiConnectionContext.selectNetwork(network)}>
|
<ListItemAvatar>
|
||||||
<ListItemAvatar>
|
<Avatar>{isNetworkOpen(network) ? <LockOpenIcon /> : <LockIcon />}</Avatar>
|
||||||
<Avatar>{isNetworkOpen(network) ? <LockOpenIcon /> : <LockIcon />}</Avatar>
|
</ListItemAvatar>
|
||||||
</ListItemAvatar>
|
<ListItemText
|
||||||
<ListItemText
|
primary={network.ssid}
|
||||||
primary={network.ssid}
|
secondary={'Security: ' + networkSecurityMode(network) + ', Ch: ' + network.channel}
|
||||||
secondary={'Security: ' + networkSecurityMode(network) + ', Ch: ' + network.channel}
|
/>
|
||||||
/>
|
<ListItemIcon>
|
||||||
<ListItemIcon>
|
<Badge badgeContent={network.rssi + 'db'}>
|
||||||
<Badge badgeContent={network.rssi + 'db'}>
|
<WifiIcon />
|
||||||
<WifiIcon />
|
</Badge>
|
||||||
</Badge>
|
</ListItemIcon>
|
||||||
</ListItemIcon>
|
</ListItem>
|
||||||
</ListItem>
|
);
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (networkList.networks.length === 0) {
|
if (networkList.networks.length === 0) {
|
||||||
return <MessageBox mt={2} mb={1} message={LL.NETWORK_NO_WIFI()} level="info" />;
|
return <MessageBox mt={2} mb={1} message={LL.NETWORK_NO_WIFI()} level="info" />;
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { FC, useState } from 'react';
|
|
||||||
import { ValidateFieldsError } from 'async-validator';
|
|
||||||
|
|
||||||
import { Button, Checkbox, MenuItem } from '@mui/material';
|
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
import { Button, Checkbox, MenuItem } from '@mui/material';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ';
|
||||||
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { validate } from 'validators';
|
import type { NTPSettings } from 'types';
|
||||||
|
import * as NTPApi from 'api/ntp';
|
||||||
import {
|
import {
|
||||||
BlockFormControlLabel,
|
BlockFormControlLabel,
|
||||||
ButtonRow,
|
ButtonRow,
|
||||||
@@ -14,13 +16,10 @@ import {
|
|||||||
ValidatedTextField,
|
ValidatedTextField,
|
||||||
BlockNavigation
|
BlockNavigation
|
||||||
} from 'components';
|
} from 'components';
|
||||||
import { NTPSettings } from 'types';
|
|
||||||
import { updateValueDirty, useRest } from 'utils';
|
|
||||||
import * as NTPApi from 'api/ntp';
|
|
||||||
import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ';
|
|
||||||
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { updateValueDirty, useRest } from 'utils';
|
||||||
|
import { validate } from 'validators';
|
||||||
|
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
|
||||||
|
|
||||||
const NTPSettingsForm: FC = () => {
|
const NTPSettingsForm: FC = () => {
|
||||||
const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
|
const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
|
||||||
@@ -44,7 +43,7 @@ const NTPSettingsForm: FC = () => {
|
|||||||
try {
|
try {
|
||||||
setFieldErrors(undefined);
|
setFieldErrors(undefined);
|
||||||
await validate(NTP_SETTINGS_VALIDATOR, data);
|
await validate(NTP_SETTINGS_VALIDATOR, data);
|
||||||
saveData();
|
await saveData();
|
||||||
} catch (errors: any) {
|
} catch (errors: any) {
|
||||||
setFieldErrors(errors);
|
setFieldErrors(errors);
|
||||||
}
|
}
|
||||||
@@ -96,7 +95,7 @@ const NTPSettingsForm: FC = () => {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => loadData()}
|
onClick={loadData}
|
||||||
>
|
>
|
||||||
{LL.CANCEL()}
|
{LL.CANCEL()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { FC, useContext, useState } from 'react';
|
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||||
import { toast } from 'react-toastify';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import DnsIcon from '@mui/icons-material/Dns';
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
|
import SwapVerticalCircleIcon from '@mui/icons-material/SwapVerticalCircle';
|
||||||
|
import UpdateIcon from '@mui/icons-material/Update';
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
@@ -15,24 +18,22 @@ import {
|
|||||||
ListItemAvatar,
|
ListItemAvatar,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
TextField,
|
TextField,
|
||||||
Theme,
|
|
||||||
useTheme,
|
useTheme,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
import { useContext, useState } from 'react';
|
||||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
import { toast } from 'react-toastify';
|
||||||
import SwapVerticalCircleIcon from '@mui/icons-material/SwapVerticalCircle';
|
import type { Theme } from '@mui/material';
|
||||||
import UpdateIcon from '@mui/icons-material/Update';
|
import type { FC } from 'react';
|
||||||
import DnsIcon from '@mui/icons-material/Dns';
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
|
||||||
|
|
||||||
|
import type { NTPStatus } from 'types';
|
||||||
import * as NTPApi from 'api/ntp';
|
import * as NTPApi from 'api/ntp';
|
||||||
import { NTPStatus, NTPSyncStatus } from 'types';
|
|
||||||
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
||||||
import { extractErrorMessage, formatDateTime, formatLocalDateTime, useRest } from 'utils';
|
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { NTPSyncStatus } from 'types';
|
||||||
|
import { extractErrorMessage, formatDateTime, formatLocalDateTime, useRest } from 'utils';
|
||||||
|
|
||||||
export const isNtpActive = ({ status }: NTPStatus) => status === NTPSyncStatus.NTP_ACTIVE;
|
export const isNtpActive = ({ status }: NTPStatus) => status === NTPSyncStatus.NTP_ACTIVE;
|
||||||
export const isNtpEnabled = ({ status }: NTPStatus) => status !== NTPSyncStatus.NTP_DISABLED;
|
export const isNtpEnabled = ({ status }: NTPStatus) => status !== NTPSyncStatus.NTP_DISABLED;
|
||||||
@@ -89,7 +90,7 @@ const NTPStatusForm: FC = () => {
|
|||||||
});
|
});
|
||||||
toast.success(LL.TIME_SET());
|
toast.success(LL.TIME_SET());
|
||||||
setSettingTime(false);
|
setSettingTime(false);
|
||||||
loadData();
|
await loadData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { FC, useContext } from 'react';
|
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Tab } from '@mui/material';
|
import { Tab } from '@mui/material';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
|
import NTPSettingsForm from './NTPSettingsForm';
|
||||||
|
import NTPStatusForm from './NTPStatusForm';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components';
|
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
|
||||||
import NTPStatusForm from './NTPStatusForm';
|
|
||||||
import NTPSettingsForm from './NTPSettingsForm';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
const NetworkTime: FC = () => {
|
const NetworkTime: FC = () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FC, useCallback, useState, useEffect } from 'react';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
@@ -10,16 +10,16 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
Button
|
Button
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
import { useCallback, useState, useEffect } from 'react';
|
||||||
|
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
|
||||||
|
|
||||||
import { extractErrorMessage } from 'utils';
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { MessageBox } from 'components';
|
import type { FC } from 'react';
|
||||||
|
import type { Token } from 'types';
|
||||||
import * as SecurityApi from 'api/security';
|
import * as SecurityApi from 'api/security';
|
||||||
import { Token } from 'types';
|
import { MessageBox } from 'components';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { extractErrorMessage } from 'utils';
|
||||||
|
|
||||||
interface GenerateTokenProps {
|
interface GenerateTokenProps {
|
||||||
username?: string;
|
username?: string;
|
||||||
@@ -42,7 +42,7 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
getToken();
|
void getToken();
|
||||||
}
|
}
|
||||||
}, [open, getToken]);
|
}, [open, getToken]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,26 @@
|
|||||||
import { FC, useContext, useState } from 'react';
|
|
||||||
|
|
||||||
import { Button, IconButton, Box } from '@mui/material';
|
|
||||||
import SaveIcon from '@mui/icons-material/Save';
|
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
|
||||||
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
|
||||||
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';
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
|
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
||||||
|
import SaveIcon from '@mui/icons-material/Save';
|
||||||
import VpnKeyIcon from '@mui/icons-material/VpnKey';
|
import VpnKeyIcon from '@mui/icons-material/VpnKey';
|
||||||
|
import { Button, IconButton, Box } from '@mui/material';
|
||||||
|
|
||||||
import { useTheme } from '@table-library/react-table-library/theme';
|
|
||||||
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } 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 * as SecurityApi from 'api/security';
|
import { useContext, useState } from 'react';
|
||||||
import { SecuritySettings, User } from 'types';
|
|
||||||
import { ButtonRow, FormLoader, MessageBox, SectionContent } from 'components';
|
|
||||||
import { createUserValidator } from 'validators';
|
|
||||||
import { useRest } from 'utils';
|
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
|
||||||
|
|
||||||
import GenerateToken from './GenerateToken';
|
import GenerateToken from './GenerateToken';
|
||||||
import UserForm from './UserForm';
|
import UserForm from './UserForm';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
import type { SecuritySettings, User } from 'types';
|
||||||
|
import * as SecurityApi from 'api/security';
|
||||||
|
import { ButtonRow, FormLoader, MessageBox, SectionContent } from 'components';
|
||||||
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { useRest } from 'utils';
|
||||||
|
import { createUserValidator } from 'validators';
|
||||||
|
|
||||||
const ManageUsersForm: FC = () => {
|
const ManageUsersForm: FC = () => {
|
||||||
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<SecuritySettings>({
|
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<SecuritySettings>({
|
||||||
@@ -60,7 +58,6 @@ const ManageUsersForm: FC = () => {
|
|||||||
border-top: 1px solid #565656;
|
border-top: 1px solid #565656;
|
||||||
border-bottom: 1px solid #565656;
|
border-bottom: 1px solid #565656;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-of-type(odd) .td {
|
&:nth-of-type(odd) .td {
|
||||||
background-color: #303030;
|
background-color: #303030;
|
||||||
}
|
}
|
||||||
@@ -126,7 +123,7 @@ const ManageUsersForm: FC = () => {
|
|||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
await saveData();
|
await saveData();
|
||||||
authenticatedContext.refresh();
|
await authenticatedContext.refresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
const user_table = data.users.map((u) => ({ ...u, id: u.username }));
|
const user_table = data.users.map((u) => ({ ...u, id: u.username }));
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
import { Navigate, Routes, Route } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Tab } from '@mui/material';
|
import { Tab } from '@mui/material';
|
||||||
|
import { Navigate, Routes, Route } from 'react-router-dom';
|
||||||
|
import ManageUsersForm from './ManageUsersForm';
|
||||||
|
import SecuritySettingsForm from './SecuritySettingsForm';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
|
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
|
||||||
|
|
||||||
import SecuritySettingsForm from './SecuritySettingsForm';
|
|
||||||
import ManageUsersForm from './ManageUsersForm';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
const Security: FC = () => {
|
const Security: FC = () => {
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { FC, useContext, useState } from 'react';
|
|
||||||
import { ValidateFieldsError } from 'async-validator';
|
|
||||||
|
|
||||||
import { Button } from '@mui/material';
|
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
import { Button } from '@mui/material';
|
||||||
|
import { useContext, useState } from 'react';
|
||||||
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import type { SecuritySettings } from 'types';
|
||||||
import * as SecurityApi from 'api/security';
|
import * as SecurityApi from 'api/security';
|
||||||
import { SecuritySettings } from 'types';
|
|
||||||
import { ButtonRow, FormLoader, MessageBox, SectionContent, ValidatedPasswordField, BlockNavigation } from 'components';
|
import { ButtonRow, FormLoader, MessageBox, SectionContent, ValidatedPasswordField, BlockNavigation } from 'components';
|
||||||
import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators';
|
|
||||||
import { updateValueDirty, useRest } from 'utils';
|
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { updateValueDirty, useRest } from 'utils';
|
||||||
|
import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators';
|
||||||
|
|
||||||
const SecuritySettingsForm: FC = () => {
|
const SecuritySettingsForm: FC = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
@@ -65,7 +65,7 @@ const SecuritySettingsForm: FC = () => {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => loadData()}
|
onClick={loadData}
|
||||||
>
|
>
|
||||||
{LL.CANCEL()}
|
{LL.CANCEL()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { FC, useState, useEffect } from 'react';
|
|
||||||
import Schema, { ValidateFieldsError } from 'async-validator';
|
|
||||||
|
|
||||||
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 type Schema from 'async-validator';
|
||||||
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import type { User } from 'types';
|
||||||
import { BlockFormControlLabel, ValidatedPasswordField, ValidatedTextField } from 'components';
|
import { BlockFormControlLabel, ValidatedPasswordField, ValidatedTextField } from 'components';
|
||||||
import { User } from 'types';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { updateValue } from 'utils';
|
import { updateValue } from 'utils';
|
||||||
import { validate } from 'validators';
|
import { validate } from 'validators';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
|
||||||
|
|
||||||
interface UserFormProps {
|
interface UserFormProps {
|
||||||
creating: boolean;
|
creating: boolean;
|
||||||
validator: Schema;
|
validator: Schema;
|
||||||
|
|||||||
@@ -1,21 +1,15 @@
|
|||||||
import { FC } from 'react';
|
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||||
import { AxiosPromise } from 'axios';
|
|
||||||
|
|
||||||
import { Typography, Button, Box } from '@mui/material';
|
import { Typography, Button, Box } from '@mui/material';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
import { FileUploadConfig } from 'api/endpoints';
|
import type { FileUploadConfig } from 'api/endpoints';
|
||||||
|
import type { AxiosPromise } from 'axios';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { SingleUpload, useFileUpload } from 'components';
|
import { SingleUpload, useFileUpload } from 'components';
|
||||||
|
|
||||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
|
||||||
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import { extractErrorMessage } from 'utils';
|
|
||||||
|
|
||||||
import * as EMSESP from 'project/api';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import * as EMSESP from 'project/api';
|
||||||
|
import { extractErrorMessage } from 'utils';
|
||||||
|
|
||||||
interface UploadFileProps {
|
interface UploadFileProps {
|
||||||
uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
||||||
@@ -122,7 +116,7 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
|
|||||||
{LL.DOWNLOAD_SETTINGS_TEXT()}
|
{LL.DOWNLOAD_SETTINGS_TEXT()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => downloadSettings()}>
|
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadSettings}>
|
||||||
{LL.SETTINGS_OF('')}
|
{LL.SETTINGS_OF('')}
|
||||||
</Button>
|
</Button>
|
||||||
<Box color="warning.main">
|
<Box color="warning.main">
|
||||||
@@ -130,12 +124,7 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
|
|||||||
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '}
|
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadCustomizations}>
|
||||||
startIcon={<DownloadIcon />}
|
|
||||||
variant="outlined"
|
|
||||||
color="primary"
|
|
||||||
onClick={() => downloadCustomizations()}
|
|
||||||
>
|
|
||||||
{LL.CUSTOMIZATIONS()}
|
{LL.CUSTOMIZATIONS()}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@@ -143,16 +132,16 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
|
|||||||
startIcon={<DownloadIcon />}
|
startIcon={<DownloadIcon />}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => downloadEntities()}
|
onClick={downloadEntities}
|
||||||
>
|
>
|
||||||
{LL.ENTITIES()}
|
{LL.CUSTOM_ENTITIES(0)}
|
||||||
</Button>
|
</Button>
|
||||||
<Box color="warning.main">
|
<Box color="warning.main">
|
||||||
<Typography mt={2} mb={1} variant="body2">
|
<Typography mt={2} mb={1} variant="body2">
|
||||||
{LL.DOWNLOAD_SCHEDULE_TEXT()}{' '}
|
{LL.DOWNLOAD_SCHEDULE_TEXT()}{' '}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => downloadSchedule()}>
|
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadSchedule}>
|
||||||
{LL.SCHEDULE(0)}
|
{LL.SCHEDULE(0)}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { FC, useState } from 'react';
|
|
||||||
|
|
||||||
import { Button, Checkbox } from '@mui/material';
|
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
import { Button, Checkbox } from '@mui/material';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import type { OTASettings } from 'types';
|
||||||
import * as SystemApi from 'api/system';
|
import * as SystemApi from 'api/system';
|
||||||
import {
|
import {
|
||||||
BlockFormControlLabel,
|
BlockFormControlLabel,
|
||||||
@@ -15,15 +17,12 @@ import {
|
|||||||
BlockNavigation
|
BlockNavigation
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
|
||||||
import { OTASettings } from 'types';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||||
|
|
||||||
import { ValidateFieldsError } from 'async-validator';
|
|
||||||
import { validate } from 'validators';
|
import { validate } from 'validators';
|
||||||
import { OTA_SETTINGS_VALIDATOR } from 'validators/system';
|
import { OTA_SETTINGS_VALIDATOR } from 'validators/system';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
|
||||||
|
|
||||||
const OTASettingsForm: FC = () => {
|
const OTASettingsForm: FC = () => {
|
||||||
const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
|
const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
|
||||||
useRest<OTASettings>({
|
useRest<OTASettings>({
|
||||||
@@ -46,7 +45,7 @@ const OTASettingsForm: FC = () => {
|
|||||||
try {
|
try {
|
||||||
setFieldErrors(undefined);
|
setFieldErrors(undefined);
|
||||||
await validate(OTA_SETTINGS_VALIDATOR, data);
|
await validate(OTA_SETTINGS_VALIDATOR, data);
|
||||||
saveData();
|
await saveData();
|
||||||
} catch (errors: any) {
|
} catch (errors: any) {
|
||||||
setFieldErrors(errors);
|
setFieldErrors(errors);
|
||||||
}
|
}
|
||||||
@@ -87,7 +86,7 @@ const OTASettingsForm: FC = () => {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => loadData()}
|
onClick={loadData}
|
||||||
>
|
>
|
||||||
{LL.CANCEL()}
|
{LL.CANCEL()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FC, useRef, useState, useEffect } from 'react';
|
import { useRef, useState, useEffect } from 'react';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import * as SystemApi from 'api/system';
|
import * as SystemApi from 'api/system';
|
||||||
import { FormLoader } from 'components';
|
import { FormLoader } from 'components';
|
||||||
@@ -30,7 +31,7 @@ const RestartMonitor: FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
poll.current();
|
void poll.current();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => () => timeoutId && clearTimeout(timeoutId), [timeoutId]);
|
useEffect(() => () => timeoutId && clearTimeout(timeoutId), [timeoutId]);
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { FC, useContext } from 'react';
|
|
||||||
import { Navigate, Routes, Route } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Tab } from '@mui/material';
|
import { Tab } from '@mui/material';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { Navigate, Routes, Route } from 'react-router-dom';
|
||||||
|
import OTASettingsForm from './OTASettingsForm';
|
||||||
|
import SystemLog from './SystemLog';
|
||||||
|
import SystemStatusForm from './SystemStatusForm';
|
||||||
|
import UploadFileForm from './UploadFileForm';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { useRouterTab, RouterTabs, useLayoutTitle, RequireAdmin } from 'components';
|
import { useRouterTab, RouterTabs, useLayoutTitle, RequireAdmin } from 'components';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import UploadFileForm from './UploadFileForm';
|
|
||||||
import SystemStatusForm from './SystemStatusForm';
|
|
||||||
import OTASettingsForm from './OTASettingsForm';
|
|
||||||
|
|
||||||
import SystemLog from './SystemLog';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,37 @@
|
|||||||
import { FC, useState, useEffect, useCallback, useLayoutEffect } from 'react';
|
|
||||||
|
|
||||||
import { Box, styled, Button, Checkbox, MenuItem, Grid, Slider, FormLabel } from '@mui/material';
|
|
||||||
|
|
||||||
import * as SystemApi from 'api/system';
|
|
||||||
import { addAccessTokenParameter } from 'api/authentication';
|
|
||||||
|
|
||||||
import { SectionContent, FormLoader, BlockFormControlLabel, ValidatedTextField } from 'components';
|
|
||||||
|
|
||||||
import { LogSettings, LogEntry, LogEntries, LogLevel } from 'types';
|
|
||||||
import { updateValue, useRest, extractErrorMessage } from 'utils';
|
|
||||||
|
|
||||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
import { Box, styled, Button, Checkbox, MenuItem, Grid, TextField } from '@mui/material';
|
||||||
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import type { LogSettings, LogEntry, LogEntries } from 'types';
|
||||||
|
import { addAccessTokenParameter } from 'api/authentication';
|
||||||
import { EVENT_SOURCE_ROOT } from 'api/endpoints';
|
import { EVENT_SOURCE_ROOT } from 'api/endpoints';
|
||||||
|
import * as SystemApi from 'api/system';
|
||||||
|
|
||||||
|
import { SectionContent, FormLoader, BlockFormControlLabel, BlockNavigation } from 'components';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { LogLevel } from 'types';
|
||||||
|
import { useRest, updateValueDirty, extractErrorMessage } from 'utils';
|
||||||
|
|
||||||
export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log';
|
export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log';
|
||||||
|
|
||||||
const useWindowSize = () => {
|
// const useWindowSize = () => {
|
||||||
const [size, setSize] = useState([0, 0]);
|
// const [size, setSize] = useState([0, 0]);
|
||||||
useLayoutEffect(() => {
|
// useLayoutEffect(() => {
|
||||||
function updateSize() {
|
// function updateSize() {
|
||||||
setSize([window.innerWidth, window.innerHeight]);
|
// setSize([window.innerWidth, window.innerHeight]);
|
||||||
}
|
// }
|
||||||
window.addEventListener('resize', updateSize);
|
// window.addEventListener('resize', updateSize);
|
||||||
updateSize();
|
// updateSize();
|
||||||
return () => window.removeEventListener('resize', updateSize);
|
// return () => window.removeEventListener('resize', updateSize);
|
||||||
}, []);
|
// }, []);
|
||||||
return size;
|
// return size;
|
||||||
};
|
// };
|
||||||
|
|
||||||
const LogEntryLine = styled('div')(({ theme }) => ({
|
const LogEntryLine = styled('div')(() => ({
|
||||||
color: '#bbbbbb',
|
color: '#bbbbbb',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
@@ -64,11 +62,11 @@ const levelLabel = (level: LogLevel) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SystemLog: FC = () => {
|
const SystemLog: FC = () => {
|
||||||
useWindowSize();
|
// useWindowSize();
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const { loadData, data, setData } = useRest<LogSettings>({
|
const { loadData, data, setData, origData, dirtyFlags, blocker, setDirtyFlags, setOrigData } = useRest<LogSettings>({
|
||||||
read: SystemApi.readLogSettings
|
read: SystemApi.readLogSettings
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,47 +89,7 @@ const SystemLog: FC = () => {
|
|||||||
return data?.compact ? label : label.padEnd(7, '\xa0');
|
return data?.compact ? label : label.padEnd(7, '\xa0');
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateFormValue = updateValue(setData);
|
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
|
||||||
|
|
||||||
const reloadPage = () => {
|
|
||||||
window.location.reload();
|
|
||||||
};
|
|
||||||
|
|
||||||
const sendSettings = async (new_max_messages: number, new_level: number) => {
|
|
||||||
if (data) {
|
|
||||||
try {
|
|
||||||
const response = await SystemApi.updateLogSettings({
|
|
||||||
level: new_level,
|
|
||||||
max_messages: new_max_messages,
|
|
||||||
compact: data.compact
|
|
||||||
});
|
|
||||||
if (response.status !== 200) {
|
|
||||||
toast.error(LL.PROBLEM_UPDATING());
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const changeLevel = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
if (data) {
|
|
||||||
setData({
|
|
||||||
...data,
|
|
||||||
level: parseInt(event.target.value)
|
|
||||||
});
|
|
||||||
sendSettings(data.max_messages, parseInt(event.target.value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const changeMaxMessages = (event: Event, value: number | number[]) => {
|
|
||||||
if (data) {
|
|
||||||
setData({
|
|
||||||
...data,
|
|
||||||
max_messages: value as number
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDownload = () => {
|
const onDownload = () => {
|
||||||
let result = '';
|
let result = '';
|
||||||
@@ -166,7 +124,7 @@ const SystemLog: FC = () => {
|
|||||||
}, [LL]);
|
}, [LL]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchLog();
|
void fetchLog();
|
||||||
}, [fetchLog]);
|
}, [fetchLog]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -174,14 +132,35 @@ const SystemLog: FC = () => {
|
|||||||
es.onmessage = onMessage;
|
es.onmessage = onMessage;
|
||||||
es.onerror = () => {
|
es.onerror = () => {
|
||||||
es.close();
|
es.close();
|
||||||
reloadPage();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
es.close();
|
es.close();
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line
|
});
|
||||||
}, []);
|
|
||||||
|
const saveSettings = async () => {
|
||||||
|
if (data) {
|
||||||
|
try {
|
||||||
|
const response = await SystemApi.updateLogSettings({
|
||||||
|
level: data.level,
|
||||||
|
max_messages: data.max_messages,
|
||||||
|
compact: data.compact
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
toast.error(LL.PROBLEM_UPDATING());
|
||||||
|
} else {
|
||||||
|
setOrigData(response.data);
|
||||||
|
setDirtyFlags([]);
|
||||||
|
toast.success(LL.UPDATED_OF(LL.SETTINGS_OF('')));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@@ -191,14 +170,14 @@ const SystemLog: FC = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Grid container spacing={3} direction="row" justifyContent="flex-start" alignItems="center">
|
<Grid container spacing={3} direction="row" justifyContent="flex-start" alignItems="center">
|
||||||
<Grid item xs={4}>
|
<Grid item xs={2}>
|
||||||
<ValidatedTextField
|
<TextField
|
||||||
name="level"
|
name="level"
|
||||||
label={LL.LOG_LEVEL()}
|
label={LL.LOG_LEVEL()}
|
||||||
value={data.level}
|
value={data.level}
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onChange={changeLevel}
|
onChange={updateFormValue}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
select
|
select
|
||||||
>
|
>
|
||||||
@@ -209,26 +188,24 @@ const SystemLog: FC = () => {
|
|||||||
<MenuItem value={6}>INFO</MenuItem>
|
<MenuItem value={6}>INFO</MenuItem>
|
||||||
<MenuItem value={7}>DEBUG</MenuItem>
|
<MenuItem value={7}>DEBUG</MenuItem>
|
||||||
<MenuItem value={9}>ALL</MenuItem>
|
<MenuItem value={9}>ALL</MenuItem>
|
||||||
</ValidatedTextField>
|
</TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={3}>
|
<Grid item xs={2}>
|
||||||
<FormLabel>{LL.BUFFER_SIZE()}</FormLabel>
|
<TextField
|
||||||
<Slider
|
|
||||||
value={data.max_messages}
|
|
||||||
valueLabelDisplay="auto"
|
|
||||||
name="max_messages"
|
name="max_messages"
|
||||||
marks={[
|
label={LL.BUFFER_SIZE()}
|
||||||
{ value: 25, label: '25' },
|
value={data.max_messages}
|
||||||
{ value: 50, label: '50' },
|
fullWidth
|
||||||
{ value: 75, label: '75' },
|
variant="outlined"
|
||||||
{ value: 100, label: '100' }
|
onChange={updateFormValue}
|
||||||
]}
|
margin="normal"
|
||||||
step={25}
|
select
|
||||||
min={25}
|
>
|
||||||
max={100}
|
<MenuItem value={25}>25</MenuItem>
|
||||||
onChange={changeMaxMessages}
|
<MenuItem value={50}>50</MenuItem>
|
||||||
onChangeCommitted={() => sendSettings(data.max_messages, data.level)}
|
<MenuItem value={75}>75</MenuItem>
|
||||||
/>
|
<MenuItem value={100}>100</MenuItem>
|
||||||
|
</TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<BlockFormControlLabel
|
<BlockFormControlLabel
|
||||||
@@ -236,16 +213,33 @@ const SystemLog: FC = () => {
|
|||||||
label={LL.COMPACT()}
|
label={LL.COMPACT()}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Box
|
||||||
|
sx={{
|
||||||
|
'& button, & a, & .MuiCard-root': {
|
||||||
|
mt: 3,
|
||||||
|
mx: 0.6
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="secondary" onClick={onDownload}>
|
<Button startIcon={<DownloadIcon />} variant="outlined" color="secondary" onClick={onDownload}>
|
||||||
{LL.EXPORT()}
|
{LL.EXPORT()}
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
{dirtyFlags && dirtyFlags.length !== 0 && (
|
||||||
|
<Button
|
||||||
|
startIcon={<WarningIcon color="warning" />}
|
||||||
|
variant="contained"
|
||||||
|
color="info"
|
||||||
|
onClick={saveSettings}
|
||||||
|
>
|
||||||
|
{LL.APPLY_CHANGES(dirtyFlags.length)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: 'black',
|
backgroundColor: 'black',
|
||||||
overflow: 'scroll',
|
overflowY: 'scroll',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: 18,
|
right: 18,
|
||||||
bottom: 18,
|
bottom: 18,
|
||||||
@@ -272,6 +266,7 @@ const SystemLog: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SectionContent title={LL.LOG_OF(LL.SYSTEM(2))} titleGutter id="log-window">
|
<SectionContent title={LL.LOG_OF(LL.SYSTEM(2))} titleGutter id="log-window">
|
||||||
|
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||||
{content()}
|
{content()}
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
import { FC, useContext, useState, useEffect } from 'react';
|
import AppsIcon from '@mui/icons-material/Apps';
|
||||||
import { toast } from 'react-toastify';
|
import BuildIcon from '@mui/icons-material/Build';
|
||||||
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import DevicesIcon from '@mui/icons-material/Devices';
|
||||||
|
import FolderIcon from '@mui/icons-material/Folder';
|
||||||
|
import MemoryIcon from '@mui/icons-material/Memory';
|
||||||
|
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
|
import SdCardAlertIcon from '@mui/icons-material/SdCardAlert';
|
||||||
|
import SdStorageIcon from '@mui/icons-material/SdStorage';
|
||||||
|
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
|
||||||
|
import ShowChartIcon from '@mui/icons-material/ShowChart';
|
||||||
|
import TimerIcon from '@mui/icons-material/Timer';
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
@@ -17,31 +28,18 @@ import {
|
|||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import DevicesIcon from '@mui/icons-material/Devices';
|
|
||||||
import ShowChartIcon from '@mui/icons-material/ShowChart';
|
|
||||||
import MemoryIcon from '@mui/icons-material/Memory';
|
|
||||||
import AppsIcon from '@mui/icons-material/Apps';
|
|
||||||
import SdStorageIcon from '@mui/icons-material/SdStorage';
|
|
||||||
import SdCardAlertIcon from '@mui/icons-material/SdCardAlert';
|
|
||||||
import FolderIcon from '@mui/icons-material/Folder';
|
|
||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
|
||||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
|
||||||
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
|
|
||||||
import BuildIcon from '@mui/icons-material/Build';
|
|
||||||
import TimerIcon from '@mui/icons-material/Timer';
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
|
||||||
|
|
||||||
import { ButtonRow, FormLoader, SectionContent, MessageBox } from 'components';
|
|
||||||
import { SystemStatus, Version } from 'types';
|
|
||||||
import * as SystemApi from 'api/system';
|
|
||||||
import { extractErrorMessage, useRest } from 'utils';
|
|
||||||
|
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { useContext, useState, useEffect } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
import RestartMonitor from './RestartMonitor';
|
import RestartMonitor from './RestartMonitor';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import type { SystemStatus, Version } from 'types';
|
||||||
|
import * as SystemApi from 'api/system';
|
||||||
|
import { ButtonRow, FormLoader, SectionContent, MessageBox } from 'components';
|
||||||
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { extractErrorMessage, useRest } from 'utils';
|
||||||
|
|
||||||
export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest';
|
export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest';
|
||||||
export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest';
|
export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest';
|
||||||
@@ -66,14 +64,14 @@ const SystemStatusForm: FC = () => {
|
|||||||
const [latestDevVersion, setLatestDevVersion] = useState<Version>();
|
const [latestDevVersion, setLatestDevVersion] = useState<Version>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
axios.get(VERSIONCHECK_ENDPOINT).then((response) => {
|
void axios.get(VERSIONCHECK_ENDPOINT).then((response) => {
|
||||||
setLatestVersion({
|
setLatestVersion({
|
||||||
version: response.data.name,
|
version: response.data.name,
|
||||||
url: response.data.assets[1].browser_download_url,
|
url: response.data.assets[1].browser_download_url,
|
||||||
changelog: response.data.assets[0].browser_download_url
|
changelog: response.data.assets[0].browser_download_url
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
axios.get(VERSIONCHECK_DEV_ENDPOINT).then((response) => {
|
void axios.get(VERSIONCHECK_DEV_ENDPOINT).then((response) => {
|
||||||
setLatestDevVersion({
|
setLatestDevVersion({
|
||||||
version: response.data.name.split(/\s+/).splice(-1),
|
version: response.data.name.split(/\s+/).splice(-1),
|
||||||
url: response.data.assets[1].browser_download_url,
|
url: response.data.assets[1].browser_download_url,
|
||||||
@@ -148,61 +146,59 @@ const SystemStatusForm: FC = () => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderVersionDialog = () => {
|
const renderVersionDialog = () => (
|
||||||
return (
|
<Dialog open={showingVersion} onClose={() => setShowingVersion(false)}>
|
||||||
<Dialog open={showingVersion} onClose={() => setShowingVersion(false)}>
|
<DialogTitle>{LL.VERSION_CHECK(1)}</DialogTitle>
|
||||||
<DialogTitle>{LL.VERSION_CHECK(1)}</DialogTitle>
|
<DialogContent dividers>
|
||||||
<DialogContent dividers>
|
<MessageBox my={0} level="info" message={LL.VERSION_ON() + ' v' + data?.emsesp_version} />
|
||||||
<MessageBox my={0} level="info" message={LL.SYSTEM_VERSION_RUNNING() + ' ' + data?.emsesp_version} />
|
{latestVersion && (
|
||||||
{latestVersion && (
|
<Box mt={2} mb={2}>
|
||||||
<Box mt={2} mb={2}>
|
{LL.THE_LATEST()} <u>{LL.OFFICIAL()}</u> {LL.RELEASE_IS()} <b>{latestVersion.version}</b>
|
||||||
{LL.THE_LATEST()} <u>{LL.OFFICIAL()}</u> {LL.VERSION_IS()} <b>{latestVersion.version}</b>
|
(
|
||||||
(
|
<Link target="_blank" href={latestVersion.changelog} color="primary">
|
||||||
<Link target="_blank" href={latestVersion.changelog} color="primary">
|
{LL.RELEASE_NOTES()}
|
||||||
{LL.RELEASE_NOTES()}
|
</Link>
|
||||||
</Link>
|
) (
|
||||||
) (
|
<Link target="_blank" href={latestVersion.url} color="primary">
|
||||||
<Link target="_blank" href={latestVersion.url} color="primary">
|
{LL.DOWNLOAD(1)}
|
||||||
{LL.DOWNLOAD(1)}
|
</Link>
|
||||||
</Link>
|
)
|
||||||
)
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{latestDevVersion && (
|
|
||||||
<Box mt={2} mb={2}>
|
|
||||||
{LL.THE_LATEST()} <u>{LL.DEVELOPMENT()}</u> {LL.VERSION_IS()}
|
|
||||||
<b>{latestDevVersion.version}</b>
|
|
||||||
(
|
|
||||||
<Link target="_blank" href={latestDevVersion.changelog} color="primary">
|
|
||||||
{LL.RELEASE_NOTES()}
|
|
||||||
</Link>
|
|
||||||
) (
|
|
||||||
<Link target="_blank" href={latestDevVersion.url} color="primary">
|
|
||||||
{LL.DOWNLOAD(1)}
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Box color="warning.main" p={0} pl={0} pr={0} mt={4} mb={0}>
|
|
||||||
<Typography variant="body2">
|
|
||||||
{LL.USE()}
|
|
||||||
<Link href={uploadURL} color="primary">
|
|
||||||
{LL.UPLOAD()}
|
|
||||||
</Link>
|
|
||||||
{LL.SYSTEM_APPLY_FIRMWARE()}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
)}
|
||||||
<DialogActions>
|
|
||||||
<Button variant="outlined" onClick={() => setShowingVersion(false)} color="secondary">
|
{latestDevVersion && (
|
||||||
{LL.CLOSE()}
|
<Box mt={2} mb={2}>
|
||||||
</Button>
|
{LL.THE_LATEST()} <u>{LL.DEVELOPMENT()}</u> {LL.RELEASE_IS()}
|
||||||
</DialogActions>
|
<b>{latestDevVersion.version}</b>
|
||||||
</Dialog>
|
(
|
||||||
);
|
<Link target="_blank" href={latestDevVersion.changelog} color="primary">
|
||||||
};
|
{LL.RELEASE_NOTES()}
|
||||||
|
</Link>
|
||||||
|
) (
|
||||||
|
<Link target="_blank" href={latestDevVersion.url} color="primary">
|
||||||
|
{LL.DOWNLOAD(1)}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box color="warning.main" p={0} pl={0} pr={0} mt={4} mb={0}>
|
||||||
|
<Typography variant="body2">
|
||||||
|
{LL.USE()}
|
||||||
|
<Link href={uploadURL} color="primary">
|
||||||
|
{LL.UPLOAD()}
|
||||||
|
</Link>
|
||||||
|
{LL.SYSTEM_APPLY_FIRMWARE()}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant="outlined" onClick={() => setShowingVersion(false)} color="secondary">
|
||||||
|
{LL.CLOSE()}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
|
||||||
const factoryReset = async () => {
|
const factoryReset = async () => {
|
||||||
setProcessing(true);
|
setProcessing(true);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { FC, useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
|
import GeneralFileUpload from './GeneralFileUpload';
|
||||||
|
import RestartMonitor from './RestartMonitor';
|
||||||
|
import type { FileUploadConfig } from 'api/endpoints';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import * as SystemApi from 'api/system';
|
import * as SystemApi from 'api/system';
|
||||||
import { SectionContent } from 'components';
|
import { SectionContent } from 'components';
|
||||||
import { FileUploadConfig } from 'api/endpoints';
|
|
||||||
|
|
||||||
import GeneralFileUpload from './GeneralFileUpload';
|
|
||||||
import RestartMonitor from './RestartMonitor';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ const de: Translation = {
|
|||||||
ENTITY_NAME: 'Entitätsname',
|
ENTITY_NAME: 'Entitätsname',
|
||||||
VALUE: '{{Wert|wert}}',
|
VALUE: '{{Wert|wert}}',
|
||||||
SHOW_FAV: 'nur Favoriten anzeigen',
|
SHOW_FAV: 'nur Favoriten anzeigen',
|
||||||
DEVICE_SENSOR_DATA: 'Geräte- und Sensordaten',
|
DEVICE_DATA: 'Gerätedaten',
|
||||||
DEVICES_SENSORS: 'Geräte & Sensoren',
|
SENSOR_DATA: 'Sensordaten',
|
||||||
ATTACHED_SENSORS: 'Angeschlossene EMS-ESP Sensoren',
|
DEVICES: 'Geräte',
|
||||||
|
SENSORS: 'Sensoren',
|
||||||
RUN_COMMAND: 'Befehl ausführen',
|
RUN_COMMAND: 'Befehl ausführen',
|
||||||
CHANGE_VALUE: 'Wert ändern',
|
CHANGE_VALUE: 'Wert ändern',
|
||||||
CANCEL: 'Abbrechen',
|
CANCEL: 'Abbrechen',
|
||||||
RESET: 'Zurücksetzen',
|
RESET: 'Zurücksetzen',
|
||||||
SEND: 'Senden',
|
|
||||||
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
|
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
|
||||||
UPDATE: 'Update', // TODO translate
|
UPDATE: 'Update', // TODO translate
|
||||||
REMOVE: 'Entfernen',
|
REMOVE: 'Entfernen',
|
||||||
@@ -174,7 +174,7 @@ const de: Translation = {
|
|||||||
SUPPORT_INFO: 'Support Info',
|
SUPPORT_INFO: 'Support Info',
|
||||||
UPLOAD_OF: '{0} Hochladen',
|
UPLOAD_OF: '{0} Hochladen',
|
||||||
UPLOAD: 'Hochladen',
|
UPLOAD: 'Hochladen',
|
||||||
DOWNLOAD: 'Herunterladen',
|
DOWNLOAD: '{{H|h|h}}erunterladen',
|
||||||
ABORTED: 'abgebrochen',
|
ABORTED: 'abgebrochen',
|
||||||
FAILED: 'gescheitert',
|
FAILED: 'gescheitert',
|
||||||
SUCCESSFUL: 'erfolgreich',
|
SUCCESSFUL: 'erfolgreich',
|
||||||
@@ -182,7 +182,7 @@ const de: Translation = {
|
|||||||
LOG_OF: '{0} Log',
|
LOG_OF: '{0} Log',
|
||||||
STATUS_OF: '{0} Status',
|
STATUS_OF: '{0} Status',
|
||||||
UPLOAD_DOWNLOAD: 'Hoch-/Herunterladen',
|
UPLOAD_DOWNLOAD: 'Hoch-/Herunterladen',
|
||||||
SYSTEM_VERSION_RUNNING: 'Sie verwenden die Version',
|
VERSION_ON: 'You are currently on', // TODO translate
|
||||||
SYSTEM_APPLY_FIRMWARE: 'um die neue Firmware anzuwenden',
|
SYSTEM_APPLY_FIRMWARE: 'um die neue Firmware anzuwenden',
|
||||||
CLOSE: 'Schließen',
|
CLOSE: 'Schließen',
|
||||||
USE: 'Verwenden Sie',
|
USE: 'Verwenden Sie',
|
||||||
@@ -193,7 +193,7 @@ const de: Translation = {
|
|||||||
THE_LATEST: 'Die neueste',
|
THE_LATEST: 'Die neueste',
|
||||||
OFFICIAL: 'offizielle',
|
OFFICIAL: 'offizielle',
|
||||||
DEVELOPMENT: 'Entwicklungs',
|
DEVELOPMENT: 'Entwicklungs',
|
||||||
VERSION_IS: 'Version ist',
|
RELEASE_IS: 'release ist', // TODO translate
|
||||||
RELEASE_NOTES: 'Versionshinweise',
|
RELEASE_NOTES: 'Versionshinweise',
|
||||||
EMS_ESP_VER: 'EMS-ESP Version',
|
EMS_ESP_VER: 'EMS-ESP Version',
|
||||||
PLATFORM: 'Platform (Platform / SDK)',
|
PLATFORM: 'Platform (Platform / SDK)',
|
||||||
@@ -311,17 +311,20 @@ const de: Translation = {
|
|||||||
LEAVE: 'Verlassen',
|
LEAVE: 'Verlassen',
|
||||||
SCHEDULER: 'Planer',
|
SCHEDULER: 'Planer',
|
||||||
SCHEDULER_HELP_1: 'Fügen Sie eigene, geplante Befehle zur Automatisierung hinzu. Vergeben Sie einen Entitätsnamen um die Aktivierung über API/Mqtt zu steuern',
|
SCHEDULER_HELP_1: 'Fügen Sie eigene, geplante Befehle zur Automatisierung hinzu. Vergeben Sie einen Entitätsnamen um die Aktivierung über API/Mqtt zu steuern',
|
||||||
SCHEDULER_HELP_2: 'Use 00:00 to trigger on boot', // TODO translate
|
SCHEDULER_HELP_2: 'Use 00:00 to trigger once on start-up', // TODO translate
|
||||||
SCHEDULE: 'Zeitplan',
|
SCHEDULE: 'Zeitplan',
|
||||||
TIME: 'Zeit',
|
TIME: 'Zeit',
|
||||||
TIMER: 'Timer',
|
TIMER: 'Timer',
|
||||||
SCHEDULE_SAVED: 'Plan gespeichert',
|
SCHEDULE_UPDATED: 'Plan gespeichert',
|
||||||
SCHEDULE_TIMER_1: 'beim Start',
|
SCHEDULE_TIMER_1: 'beim Start',
|
||||||
SCHEDULE_TIMER_2: 'jede Minute',
|
SCHEDULE_TIMER_2: 'jede Minute',
|
||||||
SCHEDULE_TIMER_3: 'jede Stunde',
|
SCHEDULE_TIMER_3: 'jede Stunde',
|
||||||
CUSTOM_ENTITIES: 'Individuelle Entitäten',
|
CUSTOM_ENTITIES: 'Individuelle Entitäten',
|
||||||
ENTITIES_HELP_1: 'Abfrage von Werten auf dem EMS-Bus',
|
ENTITIES_HELP_1: 'Abfrage von Werten auf dem EMS-Bus',
|
||||||
WRITEABLE: 'Schreibbar'
|
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
||||||
|
WRITEABLE: 'Schreibbar',
|
||||||
|
SHOWING: 'Showing', // TODO translate
|
||||||
|
SEARCH: 'Search' // TODO translate
|
||||||
};
|
};
|
||||||
|
|
||||||
export default de;
|
export default de;
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ const en: Translation = {
|
|||||||
HELP_OF: '{0} Help',
|
HELP_OF: '{0} Help',
|
||||||
LOGGED_IN: 'Logged in as {name}',
|
LOGGED_IN: 'Logged in as {name}',
|
||||||
PLEASE_SIGNIN: 'Please sign in to continue',
|
PLEASE_SIGNIN: 'Please sign in to continue',
|
||||||
UPLOAD_SUCCESSFUL: 'Upload finished',
|
UPLOAD_SUCCESSFUL: 'Upload successful',
|
||||||
DOWNLOAD_SUCCESSFUL: 'Download finished',
|
DOWNLOAD_SUCCESSFUL: 'Download successful',
|
||||||
INVALID_LOGIN: 'Invalid login details',
|
INVALID_LOGIN: 'Invalid login details',
|
||||||
NETWORK: 'Network',
|
NETWORK: 'Network',
|
||||||
SECURITY: 'Security',
|
SECURITY: 'Security',
|
||||||
@@ -38,14 +38,14 @@ const en: Translation = {
|
|||||||
ENTITY_NAME: 'Entity Name',
|
ENTITY_NAME: 'Entity Name',
|
||||||
VALUE: '{{Value|value}}',
|
VALUE: '{{Value|value}}',
|
||||||
SHOW_FAV: 'only show favorites',
|
SHOW_FAV: 'only show favorites',
|
||||||
DEVICE_SENSOR_DATA: 'Device and Sensor Data',
|
DEVICE_DATA: 'Device Data',
|
||||||
DEVICES_SENSORS: 'Devices & Sensors',
|
SENSOR_DATA: 'Sensor Data',
|
||||||
ATTACHED_SENSORS: 'Attached EMS-ESP Sensors',
|
DEVICES: 'Devices',
|
||||||
|
SENSORS: 'Sensors',
|
||||||
RUN_COMMAND: 'Call Command',
|
RUN_COMMAND: 'Call Command',
|
||||||
CHANGE_VALUE: 'Change Value',
|
CHANGE_VALUE: 'Change Value',
|
||||||
CANCEL: 'Cancel',
|
CANCEL: 'Cancel',
|
||||||
RESET: 'Reset',
|
RESET: 'Reset',
|
||||||
SEND: 'Send',
|
|
||||||
APPLY_CHANGES: 'Apply Changes ({0})',
|
APPLY_CHANGES: 'Apply Changes ({0})',
|
||||||
UPDATE: 'Update',
|
UPDATE: 'Update',
|
||||||
REMOVE: 'Remove',
|
REMOVE: 'Remove',
|
||||||
@@ -69,12 +69,12 @@ const en: Translation = {
|
|||||||
SENSOR: 'Sensor',
|
SENSOR: 'Sensor',
|
||||||
TEMP_SENSOR: 'Temperature Sensor',
|
TEMP_SENSOR: 'Temperature Sensor',
|
||||||
TEMP_SENSORS: 'Temperature Sensors',
|
TEMP_SENSORS: 'Temperature Sensors',
|
||||||
WRITE_CMD_SENT: 'Write command has been sent',
|
WRITE_CMD_SENT: 'Write command sent',
|
||||||
WRITE_CMD_FAILED: 'Write command failed',
|
WRITE_CMD_FAILED: 'Write command failed',
|
||||||
EMS_BUS_WARNING: 'EMS bus disconnected. If this warning still persists after a few seconds please check settings and board profile',
|
EMS_BUS_WARNING: 'EMS bus disconnected. If this warning still persists after a few seconds please check settings and board profile',
|
||||||
EMS_BUS_SCANNING: 'Scanning for EMS devices...',
|
EMS_BUS_SCANNING: 'Scanning for EMS devices...',
|
||||||
CONNECTED: 'Connected',
|
CONNECTED: 'Connected',
|
||||||
TX_ISSUES: 'Tx issues - try a different Tx Mode',
|
TX_ISSUES: 'Tx issues - check Tx Mode',
|
||||||
DISCONNECTED: 'Disconnected',
|
DISCONNECTED: 'Disconnected',
|
||||||
EMS_SCAN: 'Are you sure you want to initiate a full device scan of the EMS bus?',
|
EMS_SCAN: 'Are you sure you want to initiate a full device scan of the EMS bus?',
|
||||||
EMS_BUS_STATUS: 'EMS Bus Status',
|
EMS_BUS_STATUS: 'EMS Bus Status',
|
||||||
@@ -174,7 +174,7 @@ const en: Translation = {
|
|||||||
SUPPORT_INFO: 'Support Info',
|
SUPPORT_INFO: 'Support Info',
|
||||||
UPLOAD_OF: '{0} Upload',
|
UPLOAD_OF: '{0} Upload',
|
||||||
UPLOAD: 'Upload',
|
UPLOAD: 'Upload',
|
||||||
DOWNLOAD: 'Download',
|
DOWNLOAD: '{{D|d|d}}ownload',
|
||||||
ABORTED: 'aborted',
|
ABORTED: 'aborted',
|
||||||
FAILED: 'failed',
|
FAILED: 'failed',
|
||||||
SUCCESSFUL: 'successful',
|
SUCCESSFUL: 'successful',
|
||||||
@@ -182,7 +182,7 @@ const en: Translation = {
|
|||||||
LOG_OF: '{0} Log',
|
LOG_OF: '{0} Log',
|
||||||
STATUS_OF: '{0} Status',
|
STATUS_OF: '{0} Status',
|
||||||
UPLOAD_DOWNLOAD: 'Upload/Download',
|
UPLOAD_DOWNLOAD: 'Upload/Download',
|
||||||
SYSTEM_VERSION_RUNNING: 'You are currently running version',
|
VERSION_ON: 'You are currently on',
|
||||||
SYSTEM_APPLY_FIRMWARE: 'to apply the new firmware',
|
SYSTEM_APPLY_FIRMWARE: 'to apply the new firmware',
|
||||||
CLOSE: 'Close',
|
CLOSE: 'Close',
|
||||||
USE: 'Use',
|
USE: 'Use',
|
||||||
@@ -193,7 +193,7 @@ const en: Translation = {
|
|||||||
THE_LATEST: 'The latest',
|
THE_LATEST: 'The latest',
|
||||||
OFFICIAL: 'official',
|
OFFICIAL: 'official',
|
||||||
DEVELOPMENT: 'development',
|
DEVELOPMENT: 'development',
|
||||||
VERSION_IS: 'version is',
|
RELEASE_IS: 'release is',
|
||||||
RELEASE_NOTES: 'release notes',
|
RELEASE_NOTES: 'release notes',
|
||||||
EMS_ESP_VER: 'EMS-ESP Version',
|
EMS_ESP_VER: 'EMS-ESP Version',
|
||||||
PLATFORM: 'Device (Platform / SDK)',
|
PLATFORM: 'Device (Platform / SDK)',
|
||||||
@@ -315,13 +315,17 @@ const en: Translation = {
|
|||||||
SCHEDULE: 'Schedule',
|
SCHEDULE: 'Schedule',
|
||||||
TIME: 'Time',
|
TIME: 'Time',
|
||||||
TIMER: 'Timer',
|
TIMER: 'Timer',
|
||||||
SCHEDULE_SAVED: 'Schedule updated',
|
SCHEDULE_UPDATED: 'Schedule updated',
|
||||||
SCHEDULE_TIMER_1: 'on startup',
|
SCHEDULE_TIMER_1: 'on startup',
|
||||||
SCHEDULE_TIMER_2: 'every minute',
|
SCHEDULE_TIMER_2: 'every minute',
|
||||||
SCHEDULE_TIMER_3: 'every hour',
|
SCHEDULE_TIMER_3: 'every hour',
|
||||||
CUSTOM_ENTITIES: 'Custom entities',
|
CUSTOM_ENTITIES: 'Custom Entities',
|
||||||
ENTITIES_HELP_1: 'Fetch custom entities from the EMS-bus',
|
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus',
|
||||||
WRITEABLE: 'Writeable'
|
ENTITIES_UPDATED: 'Entities Updated',
|
||||||
|
WRITEABLE: 'Writeable',
|
||||||
|
SHOWING: 'Showing',
|
||||||
|
SEARCH: 'Search'
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default en;
|
export default en;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { FormattersInitializer } from 'typesafe-i18n';
|
|
||||||
import type { Locales, Formatters } from './i18n-types';
|
import type { Locales, Formatters } from './i18n-types';
|
||||||
|
import type { FormattersInitializer } from 'typesafe-i18n';
|
||||||
|
|
||||||
export const initFormatters: FormattersInitializer<Locales, Formatters> = (locale: Locales) => {
|
export const initFormatters: FormattersInitializer<Locales, Formatters> = () => {
|
||||||
const formatters: Formatters = {
|
const formatters: Formatters = {
|
||||||
// add your formatter functions here
|
// add your formatter functions here
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ const fr: Translation = {
|
|||||||
ENTITY_NAME: 'Nom de l\'entité',
|
ENTITY_NAME: 'Nom de l\'entité',
|
||||||
VALUE: 'Valeur',
|
VALUE: 'Valeur',
|
||||||
SHOW_FAV: 'ne montrer que les favoris',
|
SHOW_FAV: 'ne montrer que les favoris',
|
||||||
DEVICE_SENSOR_DATA: 'Données des appareils et capteurs',
|
DEVICE_DATA: 'Données des appareils',
|
||||||
DEVICES_SENSORS: 'Appareils et capteurs',
|
SENSOR_DATA: 'Données des capteurs',
|
||||||
ATTACHED_SENSORS: 'Capteurs EMS-ESP connectés',
|
DEVICES: 'Appareils',
|
||||||
|
SENSORS: 'Capteurs',
|
||||||
RUN_COMMAND: 'Lancer une commande',
|
RUN_COMMAND: 'Lancer une commande',
|
||||||
CHANGE_VALUE: 'Changer la valeur',
|
CHANGE_VALUE: 'Changer la valeur',
|
||||||
CANCEL: 'Annuler',
|
CANCEL: 'Annuler',
|
||||||
RESET: 'Réinitialiser',
|
RESET: 'Réinitialiser',
|
||||||
SEND: 'Envoyer',
|
|
||||||
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
|
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
|
||||||
UPDATE: 'Update', // TODO translate
|
UPDATE: 'Update', // TODO translate
|
||||||
REMOVE: 'Enlever',
|
REMOVE: 'Enlever',
|
||||||
@@ -174,7 +174,7 @@ const fr: Translation = {
|
|||||||
SUPPORT_INFO: 'Information de support',
|
SUPPORT_INFO: 'Information de support',
|
||||||
UPLOAD_OF: 'Upload de {0}',
|
UPLOAD_OF: 'Upload de {0}',
|
||||||
UPLOAD: 'Upload',
|
UPLOAD: 'Upload',
|
||||||
DOWNLOAD: 'Download',
|
DOWNLOAD: '{{D|d|d}}ownload',
|
||||||
ABORTED: 'annulé',
|
ABORTED: 'annulé',
|
||||||
FAILED: 'échoué',
|
FAILED: 'échoué',
|
||||||
SUCCESSFUL: 'réussi',
|
SUCCESSFUL: 'réussi',
|
||||||
@@ -182,7 +182,7 @@ const fr: Translation = {
|
|||||||
LOG_OF: '{0} Log',
|
LOG_OF: '{0} Log',
|
||||||
STATUS_OF: 'Statut {0}',
|
STATUS_OF: 'Statut {0}',
|
||||||
UPLOAD_DOWNLOAD: 'Upload/Download',
|
UPLOAD_DOWNLOAD: 'Upload/Download',
|
||||||
SYSTEM_VERSION_RUNNING: 'Vous utilisez actuellement la version',
|
VERSION_ON: 'You are currently on', // TODO translate
|
||||||
SYSTEM_APPLY_FIRMWARE: 'pour appliquer le nouveau firmware',
|
SYSTEM_APPLY_FIRMWARE: 'pour appliquer le nouveau firmware',
|
||||||
CLOSE: 'Fermer',
|
CLOSE: 'Fermer',
|
||||||
USE: 'Utiliser',
|
USE: 'Utiliser',
|
||||||
@@ -193,7 +193,7 @@ const fr: Translation = {
|
|||||||
THE_LATEST: 'La dernière',
|
THE_LATEST: 'La dernière',
|
||||||
OFFICIAL: 'officielle',
|
OFFICIAL: 'officielle',
|
||||||
DEVELOPMENT: 'développement',
|
DEVELOPMENT: 'développement',
|
||||||
VERSION_IS: 'version est',
|
RELEASE_IS: 'release est', // TODO translate
|
||||||
RELEASE_NOTES: 'notes de version',
|
RELEASE_NOTES: 'notes de version',
|
||||||
EMS_ESP_VER: 'Version EMS-ESP',
|
EMS_ESP_VER: 'Version EMS-ESP',
|
||||||
PLATFORM: 'Appareil (Plateforme / SDK)',
|
PLATFORM: 'Appareil (Plateforme / SDK)',
|
||||||
@@ -315,13 +315,16 @@ const fr: Translation = {
|
|||||||
SCHEDULE: 'Schedule', // TODO translate
|
SCHEDULE: 'Schedule', // TODO translate
|
||||||
TIME: 'Time', // TODO translate
|
TIME: 'Time', // TODO translate
|
||||||
TIMER: 'Timer', // TODO translate
|
TIMER: 'Timer', // TODO translate
|
||||||
SCHEDULE_SAVED: 'Schedule updated', // TODO translate
|
SCHEDULE_UPDATED: 'Schedule updated', // TODO translate
|
||||||
SCHEDULE_TIMER_1: 'on startup', // TODO translate
|
SCHEDULE_TIMER_1: 'on startup', // TODO translate
|
||||||
SCHEDULE_TIMER_2: 'every minute', // TODO translate
|
SCHEDULE_TIMER_2: 'every minute', // TODO translate
|
||||||
SCHEDULE_TIMER_3: 'every hour', // TODO translate
|
SCHEDULE_TIMER_3: 'every hour', // TODO translate
|
||||||
CUSTOM_ENTITIES: 'Custom entities', // TODO translate
|
CUSTOM_ENTITIES: 'Custom Entities', // TODO translate
|
||||||
ENTITIES_HELP_1: 'Fetch custom entities from the EMS-bus', // TODO translate
|
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate
|
||||||
WRITEABLE: 'Writeable' // TODO translate
|
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
||||||
|
WRITEABLE: 'Writeable', // TODO translate
|
||||||
|
SHOWING: 'Showing', // TODO translate
|
||||||
|
SEARCH: 'Search' // TODO translate
|
||||||
};
|
};
|
||||||
|
|
||||||
export default fr;
|
export default fr;
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ const nl: Translation = {
|
|||||||
ENTITY_NAME: 'Entiteit',
|
ENTITY_NAME: 'Entiteit',
|
||||||
VALUE: '{{Waarde|waarde}}',
|
VALUE: '{{Waarde|waarde}}',
|
||||||
SHOW_FAV: 'alleen favorieten weergeven',
|
SHOW_FAV: 'alleen favorieten weergeven',
|
||||||
DEVICE_SENSOR_DATA: 'Apparaat en Sensor data',
|
SENSOR_DATA: 'Sensor data',
|
||||||
DEVICES_SENSORS: 'Apparaten & Sensoren',
|
DEVICE_DATA: 'Apparaat data',
|
||||||
ATTACHED_SENSORS: 'Aangesloten EMS-ESP sensoren',
|
DEVICES: 'Apparaten',
|
||||||
|
SENSORS: 'Sensoren',
|
||||||
RUN_COMMAND: 'Call commando',
|
RUN_COMMAND: 'Call commando',
|
||||||
CHANGE_VALUE: 'Wijzig waarde',
|
CHANGE_VALUE: 'Wijzig waarde',
|
||||||
CANCEL: 'Annuleren',
|
CANCEL: 'Annuleren',
|
||||||
RESET: 'Reset',
|
RESET: 'Reset',
|
||||||
SEND: 'Verzenden',
|
|
||||||
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
|
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
|
||||||
UPDATE: 'Update', // TODO translate
|
UPDATE: 'Update', // TODO translate
|
||||||
REMOVE: 'Verwijderen',
|
REMOVE: 'Verwijderen',
|
||||||
@@ -136,7 +136,7 @@ const nl: Translation = {
|
|||||||
BOOLEAN_FORMAT_API: 'Boolean formaat API/MQTT',
|
BOOLEAN_FORMAT_API: 'Boolean formaat API/MQTT',
|
||||||
ENUM_FORMAT: 'Enum formaat API/MQTT',
|
ENUM_FORMAT: 'Enum formaat API/MQTT',
|
||||||
INDEX: 'Index',
|
INDEX: 'Index',
|
||||||
ENABLE_PARASITE: 'Activeer Dallas parasitaire modus',
|
ENABLE_PARASITE: 'Activeer parasitaire modus',
|
||||||
LOGGING: 'Logging',
|
LOGGING: 'Logging',
|
||||||
LOG_HEX: 'Log EMS telegrammen in hexadecimaal',
|
LOG_HEX: 'Log EMS telegrammen in hexadecimaal',
|
||||||
ENABLE_SYSLOG: 'Activeer Syslog',
|
ENABLE_SYSLOG: 'Activeer Syslog',
|
||||||
@@ -174,7 +174,7 @@ const nl: Translation = {
|
|||||||
SUPPORT_INFO: 'Support Info',
|
SUPPORT_INFO: 'Support Info',
|
||||||
UPLOAD_OF: '{0} Upload',
|
UPLOAD_OF: '{0} Upload',
|
||||||
UPLOAD: 'Upload',
|
UPLOAD: 'Upload',
|
||||||
DOWNLOAD: 'Download',
|
DOWNLOAD: '{{D|d|d}}ownload',
|
||||||
ABORTED: 'afgebroken',
|
ABORTED: 'afgebroken',
|
||||||
FAILED: 'mislukt',
|
FAILED: 'mislukt',
|
||||||
SUCCESSFUL: 'successvol',
|
SUCCESSFUL: 'successvol',
|
||||||
@@ -182,7 +182,7 @@ const nl: Translation = {
|
|||||||
LOG_OF: '{0} Log',
|
LOG_OF: '{0} Log',
|
||||||
STATUS_OF: '{0} Status',
|
STATUS_OF: '{0} Status',
|
||||||
UPLOAD_DOWNLOAD: 'Upload/Download',
|
UPLOAD_DOWNLOAD: 'Upload/Download',
|
||||||
SYSTEM_VERSION_RUNNING: 'op dit moment draai je versie',
|
VERSION_ON: 'You are currently on', // TODO translate
|
||||||
SYSTEM_APPLY_FIRMWARE: 'om de nieuwe firmware te activeren',
|
SYSTEM_APPLY_FIRMWARE: 'om de nieuwe firmware te activeren',
|
||||||
CLOSE: 'Sluiten',
|
CLOSE: 'Sluiten',
|
||||||
USE: 'Gebruik',
|
USE: 'Gebruik',
|
||||||
@@ -193,7 +193,7 @@ const nl: Translation = {
|
|||||||
THE_LATEST: 'De laatste',
|
THE_LATEST: 'De laatste',
|
||||||
OFFICIAL: 'official',
|
OFFICIAL: 'official',
|
||||||
DEVELOPMENT: 'development',
|
DEVELOPMENT: 'development',
|
||||||
VERSION_IS: 'versie is',
|
RELEASE_IS: 'release is',
|
||||||
RELEASE_NOTES: 'release notes',
|
RELEASE_NOTES: 'release notes',
|
||||||
EMS_ESP_VER: 'EMS-ESP Version',
|
EMS_ESP_VER: 'EMS-ESP Version',
|
||||||
PLATFORM: 'Apparaat (Platform / SDK)',
|
PLATFORM: 'Apparaat (Platform / SDK)',
|
||||||
@@ -315,13 +315,17 @@ const nl: Translation = {
|
|||||||
SCHEDULE: 'Schedule', // TODO translate
|
SCHEDULE: 'Schedule', // TODO translate
|
||||||
TIME: 'Time', // TODO translate
|
TIME: 'Time', // TODO translate
|
||||||
TIMER: 'Timer', // TODO translate
|
TIMER: 'Timer', // TODO translate
|
||||||
SCHEDULE_SAVED: 'Schedule updated', // TODO translate
|
SCHEDULE_UPDATED: 'Schedule updated', // TODO translate
|
||||||
SCHEDULE_TIMER_1: 'on startup', // TODO translate
|
SCHEDULE_TIMER_1: 'on startup', // TODO translate
|
||||||
SCHEDULE_TIMER_2: 'every minute', // TODO translate
|
SCHEDULE_TIMER_2: 'every minute', // TODO translate
|
||||||
SCHEDULE_TIMER_3: 'every hour', // TODO translate
|
SCHEDULE_TIMER_3: 'every hour', // TODO translate
|
||||||
CUSTOM_ENTITIES: 'Custom entities',
|
CUSTOM_ENTITIES: 'Custom Entities', // TODO translate
|
||||||
ENTITIES_HELP_1: 'Fetch custom entities from the EMS-bus',
|
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate
|
||||||
WRITEABLE: 'Writeable' // TODO translate
|
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
||||||
|
WRITEABLE: 'Writeable', // TODO translate
|
||||||
|
SHOWING: 'Showing', // TODO translate
|
||||||
|
SEARCH: 'Zoek'
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nl;
|
export default nl;
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ const no: Translation = {
|
|||||||
ENTITY_NAME: 'Objektsnavn',
|
ENTITY_NAME: 'Objektsnavn',
|
||||||
VALUE: '{{Verdi|verdi}}',
|
VALUE: '{{Verdi|verdi}}',
|
||||||
SHOW_FAV: ' Vis kun favoritter',
|
SHOW_FAV: ' Vis kun favoritter',
|
||||||
DEVICE_SENSOR_DATA: 'Enheter og Sensordata',
|
DEVICE_DATA: 'Enheterdata',
|
||||||
DEVICES_SENSORS: 'Enheter og Sensorer',
|
SENSOR_DATA: 'Sensordata',
|
||||||
ATTACHED_SENSORS: 'Tilkoblede EMS-ESP Sensorer',
|
DEVICES: 'Enheter',
|
||||||
|
SENSORS: 'Sensorer',
|
||||||
RUN_COMMAND: 'Kjør kommando',
|
RUN_COMMAND: 'Kjør kommando',
|
||||||
CHANGE_VALUE: 'Endre Verdi',
|
CHANGE_VALUE: 'Endre Verdi',
|
||||||
CANCEL: 'Avbryt',
|
CANCEL: 'Avbryt',
|
||||||
RESET: 'Nullstill',
|
RESET: 'Nullstill',
|
||||||
SEND: 'Send',
|
|
||||||
APPLY_CHANGES: 'Utfør endringer({0})',
|
APPLY_CHANGES: 'Utfør endringer({0})',
|
||||||
UPDATE: 'Oppdater',
|
UPDATE: 'Oppdater',
|
||||||
REMOVE: 'Fjern',
|
REMOVE: 'Fjern',
|
||||||
@@ -174,7 +174,7 @@ const no: Translation = {
|
|||||||
SUPPORT_INFO: 'Supportinfo',
|
SUPPORT_INFO: 'Supportinfo',
|
||||||
UPLOAD_OF: '{0} Opplasning',
|
UPLOAD_OF: '{0} Opplasning',
|
||||||
UPLOAD: 'Opplasning',
|
UPLOAD: 'Opplasning',
|
||||||
DOWNLOAD: 'Nedlasting',
|
DOWNLOAD: '{{N|n|n}}edlasting',
|
||||||
ABORTED: 'avbrutt',
|
ABORTED: 'avbrutt',
|
||||||
FAILED: 'feilet',
|
FAILED: 'feilet',
|
||||||
SUCCESSFUL: 'vellykket',
|
SUCCESSFUL: 'vellykket',
|
||||||
@@ -182,7 +182,7 @@ const no: Translation = {
|
|||||||
LOG_OF: '{0} Logg',
|
LOG_OF: '{0} Logg',
|
||||||
STATUS_OF: '{0} Status',
|
STATUS_OF: '{0} Status',
|
||||||
UPLOAD_DOWNLOAD: 'Opp/Nedlasting',
|
UPLOAD_DOWNLOAD: 'Opp/Nedlasting',
|
||||||
SYSTEM_VERSION_RUNNING: 'Du benytter versjon',
|
VERSION_ON: 'You are currently on', // TODO translate
|
||||||
SYSTEM_APPLY_FIRMWARE: 'for å aktivere ny firmware',
|
SYSTEM_APPLY_FIRMWARE: 'for å aktivere ny firmware',
|
||||||
CLOSE: 'Steng',
|
CLOSE: 'Steng',
|
||||||
USE: 'Bruk',
|
USE: 'Bruk',
|
||||||
@@ -193,7 +193,7 @@ const no: Translation = {
|
|||||||
THE_LATEST: 'Den nyeste',
|
THE_LATEST: 'Den nyeste',
|
||||||
OFFICIAL: 'official',
|
OFFICIAL: 'official',
|
||||||
DEVELOPMENT: 'development',
|
DEVELOPMENT: 'development',
|
||||||
VERSION_IS: 'versjonen er',
|
RELEASE_IS: 'release er',
|
||||||
RELEASE_NOTES: 'release notes',
|
RELEASE_NOTES: 'release notes',
|
||||||
EMS_ESP_VER: 'EMS-ESP Version',
|
EMS_ESP_VER: 'EMS-ESP Version',
|
||||||
PLATFORM: 'Enhet (Platform / SDK)',
|
PLATFORM: 'Enhet (Platform / SDK)',
|
||||||
@@ -315,13 +315,17 @@ const no: Translation = {
|
|||||||
SCHEDULE: 'Planlegg',
|
SCHEDULE: 'Planlegg',
|
||||||
TIME: 'Tid',
|
TIME: 'Tid',
|
||||||
TIMER: 'Timer',
|
TIMER: 'Timer',
|
||||||
SCHEDULE_SAVED: 'Planlegger er oppdatert',
|
SCHEDULE_UPDATED: 'Planlegger er oppdatert',
|
||||||
SCHEDULE_TIMER_1: 'ved oppstart',
|
SCHEDULE_TIMER_1: 'ved oppstart',
|
||||||
SCHEDULE_TIMER_2: 'hvert minutt',
|
SCHEDULE_TIMER_2: 'hvert minutt',
|
||||||
SCHEDULE_TIMER_3: 'hver time',
|
SCHEDULE_TIMER_3: 'hver time',
|
||||||
CUSTOM_ENTITIES: 'Custom entities',
|
CUSTOM_ENTITIES: 'Custom Entities', // TODO translate
|
||||||
ENTITIES_HELP_1: 'Fetch custom entities from the EMS-bus',
|
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate
|
||||||
WRITEABLE: 'Writeable' // TODO translate
|
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
||||||
|
WRITEABLE: 'Writeable', // TODO translate
|
||||||
|
SHOWING: 'Showing', // TODO translate
|
||||||
|
SEARCH: 'Search' // TODO translate
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default no;
|
export default no;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const pl: BaseTranslation = {
|
|||||||
SECURITY: '{{B|b|}}ezpieczeństw{{o|a|}}',
|
SECURITY: '{{B|b|}}ezpieczeństw{{o|a|}}',
|
||||||
ONOFF_CAP: 'wł./wył.',
|
ONOFF_CAP: 'wł./wył.',
|
||||||
ONOFF: 'włączono/wyłączono',
|
ONOFF: 'włączono/wyłączono',
|
||||||
TYPE: 'Typ',
|
TYPE: '{{T|t|}}yp{{|u|}}',
|
||||||
DESCRIPTION: 'Opis',
|
DESCRIPTION: 'Opis',
|
||||||
ENTITIES: 'Encje',
|
ENTITIES: 'Encje',
|
||||||
REFRESH: 'Odśwież',
|
REFRESH: 'Odśwież',
|
||||||
@@ -38,14 +38,14 @@ const pl: BaseTranslation = {
|
|||||||
ENTITY_NAME: 'Nazwa encji',
|
ENTITY_NAME: 'Nazwa encji',
|
||||||
VALUE: '{{W|w|}}artość',
|
VALUE: '{{W|w|}}artość',
|
||||||
SHOW_FAV: 'Pokaż tylko "ulubione"',
|
SHOW_FAV: 'Pokaż tylko "ulubione"',
|
||||||
DEVICE_SENSOR_DATA: 'Dane z urządzeń i czujników',
|
DEVICE_DATA: 'Dane z urządzeń',
|
||||||
DEVICES_SENSORS: 'Urządzenia i czujniki',
|
SENSOR_DATA: 'Dane z czujników',
|
||||||
ATTACHED_SENSORS: 'Urządzenia podłączone do EMS-ESP (czujniki temperatury/analogowe/cyfrowe, wyjścia cyfrowe)',
|
DEVICES: 'Urządzenia',
|
||||||
|
SENSORS: 'Czujniki',
|
||||||
RUN_COMMAND: 'Wykonaj komendę',
|
RUN_COMMAND: 'Wykonaj komendę',
|
||||||
CHANGE_VALUE: 'Zmień wartość',
|
CHANGE_VALUE: 'Zmień wartość',
|
||||||
CANCEL: 'Anuluj',
|
CANCEL: 'Anuluj',
|
||||||
RESET: 'Reset{{uj|owanie|}}',
|
RESET: 'Reset{{uj|owanie|}}',
|
||||||
SEND: 'Wyślij',
|
|
||||||
APPLY_CHANGES: 'Zapisz zmiany ({0})',
|
APPLY_CHANGES: 'Zapisz zmiany ({0})',
|
||||||
UPDATE: 'Uaktualnij',
|
UPDATE: 'Uaktualnij',
|
||||||
REMOVE: 'Usuń',
|
REMOVE: 'Usuń',
|
||||||
@@ -182,7 +182,7 @@ const pl: BaseTranslation = {
|
|||||||
LOG_OF: 'Log {0}',
|
LOG_OF: 'Log {0}',
|
||||||
STATUS_OF: 'Status {0}',
|
STATUS_OF: 'Status {0}',
|
||||||
UPLOAD_DOWNLOAD: 'Przesyłanie plików',
|
UPLOAD_DOWNLOAD: 'Przesyłanie plików',
|
||||||
SYSTEM_VERSION_RUNNING: 'Obecnie zainstalowana wersja to:',
|
VERSION_ON: 'You are currently on', // TODO translate
|
||||||
SYSTEM_APPLY_FIRMWARE: '',
|
SYSTEM_APPLY_FIRMWARE: '',
|
||||||
CLOSE: 'Zamknij',
|
CLOSE: 'Zamknij',
|
||||||
USE: 'Aby zaktualizować firmware skorzystaj z funkcji',
|
USE: 'Aby zaktualizować firmware skorzystaj z funkcji',
|
||||||
@@ -193,7 +193,7 @@ const pl: BaseTranslation = {
|
|||||||
THE_LATEST: 'Najnowsza',
|
THE_LATEST: 'Najnowsza',
|
||||||
OFFICIAL: 'oficjalna',
|
OFFICIAL: 'oficjalna',
|
||||||
DEVELOPMENT: 'testowa',
|
DEVELOPMENT: 'testowa',
|
||||||
VERSION_IS: 'wersja to',
|
RELEASE_IS: 'release to', // TODO translate
|
||||||
RELEASE_NOTES: 'lista zmian',
|
RELEASE_NOTES: 'lista zmian',
|
||||||
EMS_ESP_VER: 'Wersja EMS-ESP',
|
EMS_ESP_VER: 'Wersja EMS-ESP',
|
||||||
PLATFORM: 'Urządzenie (platforma / SDK)',
|
PLATFORM: 'Urządzenie (platforma / SDK)',
|
||||||
@@ -240,7 +240,7 @@ const pl: BaseTranslation = {
|
|||||||
MQTT_RESPONSE: 'Rezultat wykonania komendy publikuj w temacie "response"',
|
MQTT_RESPONSE: 'Rezultat wykonania komendy publikuj w temacie "response"',
|
||||||
MQTT_PUBLISH_TEXT_1: 'Tematy z pojedynczą wartością publikuj po jej zmianie',
|
MQTT_PUBLISH_TEXT_1: 'Tematy z pojedynczą wartością publikuj po jej zmianie',
|
||||||
MQTT_PUBLISH_TEXT_2: 'Publikuj w tematach "command" (ioBroker)',
|
MQTT_PUBLISH_TEXT_2: 'Publikuj w tematach "command" (ioBroker)',
|
||||||
MQTT_PUBLISH_TEXT_3: 'Włącz opcję "MQTT discovery',
|
MQTT_PUBLISH_TEXT_3: 'Włącz opcję "MQTT discovery"',
|
||||||
MQTT_PUBLISH_TEXT_4: 'Prefiks dla "MQTT discovery"',
|
MQTT_PUBLISH_TEXT_4: 'Prefiks dla "MQTT discovery"',
|
||||||
MQTT_PUBLISH_TEXT_5: 'Typ "MQTT discovery"',
|
MQTT_PUBLISH_TEXT_5: 'Typ "MQTT discovery"',
|
||||||
MQTT_PUBLISH_INTERVALS: 'Interwały publikowania',
|
MQTT_PUBLISH_INTERVALS: 'Interwały publikowania',
|
||||||
@@ -287,7 +287,7 @@ const pl: BaseTranslation = {
|
|||||||
NETWORK_BLANK_SSID: 'pozostaw puste aby wyłączyć WiFi',
|
NETWORK_BLANK_SSID: 'pozostaw puste aby wyłączyć WiFi',
|
||||||
TX_POWER: 'Moc nadawania',
|
TX_POWER: 'Moc nadawania',
|
||||||
HOSTNAME: 'Nazwa w sieci',
|
HOSTNAME: 'Nazwa w sieci',
|
||||||
NETWORK_DISABLE_SLEEP: 'Wyłącz tryb usypiania WiFi',
|
NETWORK_DISABLE_SLEEP: 'Wyłącz tryb uśpienia WiFi',
|
||||||
NETWORK_LOW_BAND: 'Używaj mniejszej szerokości pasma WiFi (20MHz)',
|
NETWORK_LOW_BAND: 'Używaj mniejszej szerokości pasma WiFi (20MHz)',
|
||||||
NETWORK_USE_DNS: 'Włącz wsparcie dla mDNS',
|
NETWORK_USE_DNS: 'Włącz wsparcie dla mDNS',
|
||||||
NETWORK_ENABLE_CORS: 'Włącz wsparcie dla CORS',
|
NETWORK_ENABLE_CORS: 'Włącz wsparcie dla CORS',
|
||||||
@@ -300,7 +300,7 @@ const pl: BaseTranslation = {
|
|||||||
ADDRESS_OF: 'Adres {0}',
|
ADDRESS_OF: 'Adres {0}',
|
||||||
ADMIN: 'Użytkownik "administrator".',
|
ADMIN: 'Użytkownik "administrator".',
|
||||||
GUEST: 'Użytkownik "gość".',
|
GUEST: 'Użytkownik "gość".',
|
||||||
NEW: 'nowego',
|
NEW: 'nowe{{go|j|}}',
|
||||||
NEW_NAME_OF: 'Nowa nazwa {0}',
|
NEW_NAME_OF: 'Nowa nazwa {0}',
|
||||||
ENTITY: 'encji',
|
ENTITY: 'encji',
|
||||||
MIN: 'Min.',
|
MIN: 'Min.',
|
||||||
@@ -315,13 +315,17 @@ const pl: BaseTranslation = {
|
|||||||
SCHEDULE: '{{H|h|}}armonogram{{|u|}}',
|
SCHEDULE: '{{H|h|}}armonogram{{|u|}}',
|
||||||
TIME: '{{Zegar|Godzina|}}',
|
TIME: '{{Zegar|Godzina|}}',
|
||||||
TIMER: '{{m|M|}}inutnik',
|
TIMER: '{{m|M|}}inutnik',
|
||||||
SCHEDULE_SAVED: 'Harmonogram został uaktualniony.',
|
SCHEDULE_UPDATED: 'Harmonogram został uaktualniony.',
|
||||||
SCHEDULE_TIMER_1: 'przy starcie',
|
SCHEDULE_TIMER_1: 'przy starcie',
|
||||||
SCHEDULE_TIMER_2: 'co minutę',
|
SCHEDULE_TIMER_2: 'co minutę',
|
||||||
SCHEDULE_TIMER_3: 'co godzinę',
|
SCHEDULE_TIMER_3: 'co godzinę',
|
||||||
CUSTOM_ENTITIES: 'Custom entities',
|
CUSTOM_ENTITIES: '{{N|n|}}iestandardowe{{|j|}} encj{{e|i|}}',
|
||||||
ENTITIES_HELP_1: 'Fetch custom entities from the EMS-bus',
|
ENTITIES_HELP_1: 'Zdefiniuj niestandardowe encje pobierane z magistrali EMS.',
|
||||||
WRITEABLE: 'Writeable' // TODO translate
|
ENTITIES_UPDATED: 'Niestandardowe encje zostały uaktualnione.',
|
||||||
|
WRITEABLE: 'Writeable', // TODO translate
|
||||||
|
SHOWING: 'Showing', // TODO translate
|
||||||
|
SEARCH: 'Search' // TODO translate
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default pl;
|
export default pl;
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ const sv: Translation = {
|
|||||||
ENTITY_NAME: 'Entitetsnamn',
|
ENTITY_NAME: 'Entitetsnamn',
|
||||||
VALUE: '{{Värde|värde}}',
|
VALUE: '{{Värde|värde}}',
|
||||||
SHOW_FAV: 'Visa enbart favoriter',
|
SHOW_FAV: 'Visa enbart favoriter',
|
||||||
DEVICE_SENSOR_DATA: 'Enhets och Sensor-data',
|
DEVICE_DATA: 'Enhets data',
|
||||||
DEVICES_SENSORS: 'Enheter & Sensorer',
|
SENSOR_DATA: 'Sensor data',
|
||||||
ATTACHED_SENSORS: 'Anslutna EMS-ESP Sensorer',
|
DEVICES: 'Enheter',
|
||||||
|
SENSORS: 'Sensorer',
|
||||||
RUN_COMMAND: 'Kör Kommando',
|
RUN_COMMAND: 'Kör Kommando',
|
||||||
CHANGE_VALUE: 'Ändra Värde',
|
CHANGE_VALUE: 'Ändra Värde',
|
||||||
CANCEL: 'Avbryt',
|
CANCEL: 'Avbryt',
|
||||||
RESET: 'Nollställ',
|
RESET: 'Nollställ',
|
||||||
SEND: 'Skicka',
|
|
||||||
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
|
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
|
||||||
UPDATE: 'Update', // TODO translate
|
UPDATE: 'Update', // TODO translate
|
||||||
REMOVE: 'Ta bort',
|
REMOVE: 'Ta bort',
|
||||||
@@ -174,7 +174,7 @@ const sv: Translation = {
|
|||||||
SUPPORT_INFO: 'Supportinfo',
|
SUPPORT_INFO: 'Supportinfo',
|
||||||
UPLOAD_OF: '{0} Uppladdning',
|
UPLOAD_OF: '{0} Uppladdning',
|
||||||
UPLOAD: 'Uppladdning',
|
UPLOAD: 'Uppladdning',
|
||||||
DOWNLOAD: 'Nedladdning',
|
DOWNLOAD: '{{N|n|n}}edladdning',
|
||||||
ABORTED: 'Avbruten',
|
ABORTED: 'Avbruten',
|
||||||
FAILED: 'Misslyckades',
|
FAILED: 'Misslyckades',
|
||||||
SUCCESSFUL: 'Lyckades',
|
SUCCESSFUL: 'Lyckades',
|
||||||
@@ -182,7 +182,7 @@ const sv: Translation = {
|
|||||||
LOG_OF: '{0} Logg',
|
LOG_OF: '{0} Logg',
|
||||||
STATUS_OF: '{0} Status',
|
STATUS_OF: '{0} Status',
|
||||||
UPLOAD_DOWNLOAD: 'Upp/Nedladdning',
|
UPLOAD_DOWNLOAD: 'Upp/Nedladdning',
|
||||||
SYSTEM_VERSION_RUNNING: 'Du använder version',
|
VERSION_ON: 'You are currently on', // TODO translate
|
||||||
SYSTEM_APPLY_FIRMWARE: 'för att aktivera ny firmware',
|
SYSTEM_APPLY_FIRMWARE: 'för att aktivera ny firmware',
|
||||||
CLOSE: 'Stäng',
|
CLOSE: 'Stäng',
|
||||||
USE: 'Använd',
|
USE: 'Använd',
|
||||||
@@ -193,7 +193,7 @@ const sv: Translation = {
|
|||||||
THE_LATEST: 'Den senaste',
|
THE_LATEST: 'Den senaste',
|
||||||
OFFICIAL: 'officiell',
|
OFFICIAL: 'officiell',
|
||||||
DEVELOPMENT: 'utveckling',
|
DEVELOPMENT: 'utveckling',
|
||||||
VERSION_IS: 'version är',
|
RELEASE_IS: 'release är', // TODO translate
|
||||||
RELEASE_NOTES: 'release-logg',
|
RELEASE_NOTES: 'release-logg',
|
||||||
EMS_ESP_VER: 'EMS-ESP Version',
|
EMS_ESP_VER: 'EMS-ESP Version',
|
||||||
PLATFORM: 'Enhet (Plattform / SDK)',
|
PLATFORM: 'Enhet (Plattform / SDK)',
|
||||||
@@ -315,13 +315,17 @@ const sv: Translation = {
|
|||||||
SCHEDULE: 'Schedule', // TODO translate
|
SCHEDULE: 'Schedule', // TODO translate
|
||||||
TIME: 'Time', // TODO translate
|
TIME: 'Time', // TODO translate
|
||||||
TIMER: 'Timer', // TODO translate
|
TIMER: 'Timer', // TODO translate
|
||||||
SCHEDULE_SAVED: 'Schedule updated', // TODO translate
|
SCHEDULE_UPDATED: 'Schedule updated', // TODO translate
|
||||||
SCHEDULE_TIMER_1: 'on startup', // TODO translate
|
SCHEDULE_TIMER_1: 'on startup', // TODO translate
|
||||||
SCHEDULE_TIMER_2: 'every minute', // TODO translate
|
SCHEDULE_TIMER_2: 'every minute', // TODO translate
|
||||||
SCHEDULE_TIMER_3: 'every hour', // TODO translate
|
SCHEDULE_TIMER_3: 'every hour', // TODO translate
|
||||||
CUSTOM_ENTITIES: 'Custom entities',
|
CUSTOM_ENTITIES: 'Custom Entities', // TODO translate
|
||||||
ENTITIES_HELP_1: 'Fetch custom entities from the EMS-bus',
|
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate
|
||||||
WRITEABLE: 'Writeable' // TODO translate
|
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
||||||
|
WRITEABLE: 'Writeable', // TODO translate
|
||||||
|
SHOWING: 'Showing', // TODO translate
|
||||||
|
SEARCH: 'Search' // TODO translate
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default sv;
|
export default sv;
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ const tr: Translation = {
|
|||||||
ENTITY_NAME: 'Valık Adı',
|
ENTITY_NAME: 'Valık Adı',
|
||||||
VALUE: '{{Değer|değer}}',
|
VALUE: '{{Değer|değer}}',
|
||||||
SHOW_FAV: 'sadece favorileri göster',
|
SHOW_FAV: 'sadece favorileri göster',
|
||||||
DEVICE_SENSOR_DATA: 'Cihaz ve Sensör Bilgisi',
|
DEVICE_DATA: 'Cihaz Bilgisi',
|
||||||
DEVICES_SENSORS: 'Cihazlar & Sensörler',
|
SENSOR_DATA: 'Sensör Bilgisi',
|
||||||
ATTACHED_SENSORS: 'Eklenmiş EMS-ESP Sensörler',
|
DEVICES: 'Cihazlar',
|
||||||
|
SENSORS: 'Sensörler',
|
||||||
RUN_COMMAND: 'Çalıştırma Komutu',
|
RUN_COMMAND: 'Çalıştırma Komutu',
|
||||||
CHANGE_VALUE: 'Değeri Değiştir',
|
CHANGE_VALUE: 'Değeri Değiştir',
|
||||||
CANCEL: 'İptal',
|
CANCEL: 'İptal',
|
||||||
RESET: 'Reset',
|
RESET: 'Reset',
|
||||||
SEND: 'Gönder',
|
|
||||||
APPLY_CHANGES: 'Apply Changes ({0})',
|
APPLY_CHANGES: 'Apply Changes ({0})',
|
||||||
UPDATE: 'Update',
|
UPDATE: 'Update',
|
||||||
REMOVE: 'Kaldır',
|
REMOVE: 'Kaldır',
|
||||||
@@ -174,7 +174,7 @@ const tr: Translation = {
|
|||||||
SUPPORT_INFO: 'Destek Bilgisi',
|
SUPPORT_INFO: 'Destek Bilgisi',
|
||||||
UPLOAD_OF: '{0} Yüklemesi',
|
UPLOAD_OF: '{0} Yüklemesi',
|
||||||
UPLOAD: 'Yükleme',
|
UPLOAD: 'Yükleme',
|
||||||
DOWNLOAD: 'İndirme',
|
DOWNLOAD: '{{İ|i|i}}İndirme',
|
||||||
ABORTED: 'iptal edildi',
|
ABORTED: 'iptal edildi',
|
||||||
FAILED: 'başarısız',
|
FAILED: 'başarısız',
|
||||||
SUCCESSFUL: 'başarılı',
|
SUCCESSFUL: 'başarılı',
|
||||||
@@ -182,7 +182,7 @@ const tr: Translation = {
|
|||||||
LOG_OF: '{0} Kaydı',
|
LOG_OF: '{0} Kaydı',
|
||||||
STATUS_OF: '{0} Durumu',
|
STATUS_OF: '{0} Durumu',
|
||||||
UPLOAD_DOWNLOAD: 'Yükleme/İndirme',
|
UPLOAD_DOWNLOAD: 'Yükleme/İndirme',
|
||||||
SYSTEM_VERSION_RUNNING: 'Şu anda çalıştırdığınız sürüm',
|
VERSION_ON: 'You are currently on', // TODO translate
|
||||||
SYSTEM_APPLY_FIRMWARE: 'yeni bellenimi uygulamak için',
|
SYSTEM_APPLY_FIRMWARE: 'yeni bellenimi uygulamak için',
|
||||||
CLOSE: 'Kapat',
|
CLOSE: 'Kapat',
|
||||||
USE: 'KUllan',
|
USE: 'KUllan',
|
||||||
@@ -193,7 +193,7 @@ const tr: Translation = {
|
|||||||
THE_LATEST: 'En son',
|
THE_LATEST: 'En son',
|
||||||
OFFICIAL: 'resmi',
|
OFFICIAL: 'resmi',
|
||||||
DEVELOPMENT: 'geliştirme',
|
DEVELOPMENT: 'geliştirme',
|
||||||
VERSION_IS: 'sürüm: ',
|
RELEASE_IS: 'release is', // TODO translate
|
||||||
RELEASE_NOTES: 'yayınlanma notları',
|
RELEASE_NOTES: 'yayınlanma notları',
|
||||||
EMS_ESP_VER: 'EMS-ESP Sürümü',
|
EMS_ESP_VER: 'EMS-ESP Sürümü',
|
||||||
PLATFORM: 'Cihaz (Platform / SDK)',
|
PLATFORM: 'Cihaz (Platform / SDK)',
|
||||||
@@ -315,13 +315,17 @@ const tr: Translation = {
|
|||||||
SCHEDULE: 'Schedule', // TODO translate
|
SCHEDULE: 'Schedule', // TODO translate
|
||||||
TIME: 'Time', // TODO translate
|
TIME: 'Time', // TODO translate
|
||||||
TIMER: 'Timer', // TODO translate
|
TIMER: 'Timer', // TODO translate
|
||||||
SCHEDULE_SAVED: 'Schedule updated', // TODO translate
|
SCHEDULE_UPDATED: 'Schedule updated', // TODO translate
|
||||||
SCHEDULE_TIMER_1: 'on startup', // TODO translate
|
SCHEDULE_TIMER_1: 'on startup', // TODO translate
|
||||||
SCHEDULE_TIMER_2: 'every minute', // TODO translate
|
SCHEDULE_TIMER_2: 'every minute', // TODO translate
|
||||||
SCHEDULE_TIMER_3: 'every hour', // TODO translate
|
SCHEDULE_TIMER_3: 'every hour', // TODO translate
|
||||||
CUSTOM_ENTITIES: 'Custom entities',
|
CUSTOM_ENTITIES: 'Custom Entities', // TODO translate
|
||||||
ENTITIES_HELP_1: 'Fetch custom entities from the EMS-bus',
|
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate
|
||||||
WRITEABLE: 'Writeable' // TODO translate
|
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
||||||
|
WRITEABLE: 'Writeable', // TODO translate
|
||||||
|
SHOWING: 'Showing', // TODO translate
|
||||||
|
SEARCH: 'Search' // TODO translate
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default tr;
|
export default tr;
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { FC } from 'react';
|
import { Tab } from '@mui/material';
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
import { Tab } from '@mui/material';
|
import DashboardDevices from './DashboardDevices';
|
||||||
|
import DashboardSensors from './DashboardSensors';
|
||||||
|
import DashboardStatus from './DashboardStatus';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
|
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
import DashboardStatus from './DashboardStatus';
|
|
||||||
import DashboardData from './DashboardData';
|
|
||||||
|
|
||||||
const Dashboard: FC = () => {
|
const Dashboard: FC = () => {
|
||||||
const { routerTab } = useRouterTab();
|
const { routerTab } = useRouterTab();
|
||||||
|
|
||||||
@@ -19,13 +20,15 @@ const Dashboard: FC = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RouterTabs value={routerTab}>
|
<RouterTabs value={routerTab}>
|
||||||
<Tab value="data" label={LL.DEVICES_SENSORS()} />
|
<Tab value="devices" label={LL.DEVICES()} />
|
||||||
|
<Tab value="sensors" label={LL.SENSORS()} />
|
||||||
<Tab value="status" label="Status" />
|
<Tab value="status" label="Status" />
|
||||||
</RouterTabs>
|
</RouterTabs>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="data" element={<DashboardData />} />
|
<Route path="devices" element={<DashboardDevices />} />
|
||||||
|
<Route path="sensors" element={<DashboardSensors />} />
|
||||||
<Route path="status" element={<DashboardStatus />} />
|
<Route path="status" element={<DashboardStatus />} />
|
||||||
<Route path="/*" element={<Navigate replace to="data" />} />
|
<Route path="/*" element={<Navigate replace to="devices" />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
566
interface/src/project/DashboardDevices.tsx
Normal file
566
interface/src/project/DashboardDevices.tsx
Normal file
@@ -0,0 +1,566 @@
|
|||||||
|
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
||||||
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
|
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
|
||||||
|
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||||
|
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||||
|
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
|
||||||
|
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
|
||||||
|
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
|
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
|
||||||
|
import StarIcon from '@mui/icons-material/Star';
|
||||||
|
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
IconButton,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemText,
|
||||||
|
FormControlLabel,
|
||||||
|
Checkbox,
|
||||||
|
Box
|
||||||
|
} from '@mui/material';
|
||||||
|
import { useRowSelect } from '@table-library/react-table-library/select';
|
||||||
|
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
|
||||||
|
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||||
|
import { useTheme } from '@table-library/react-table-library/theme';
|
||||||
|
import { useState, useContext, useEffect, useCallback } from 'react';
|
||||||
|
|
||||||
|
import { IconContext } from 'react-icons';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import DashboarDevicesDialog from './DashboardDevicesDialog';
|
||||||
|
import DeviceIcon from './DeviceIcon';
|
||||||
|
|
||||||
|
import * as EMSESP from './api';
|
||||||
|
import { formatValue } from './deviceValue';
|
||||||
|
|
||||||
|
import { DeviceValueUOM_s, DeviceEntityMask, DeviceType } from './types';
|
||||||
|
import { deviceValueItemValidation } from './validators';
|
||||||
|
import type { Device, CoreData, DeviceData, DeviceValue } from './types';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
import { ButtonRow, SectionContent, MessageBox } from 'components';
|
||||||
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
|
||||||
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { extractErrorMessage } from 'utils';
|
||||||
|
|
||||||
|
const topOffset = () => document.getElementById('devices-window')?.getBoundingClientRect().bottom || 0;
|
||||||
|
const leftOffset = () => document.getElementById('devices-window')?.getBoundingClientRect().left || 0;
|
||||||
|
|
||||||
|
const DashboardDevices: FC = () => {
|
||||||
|
const { me } = useContext(AuthenticatedContext);
|
||||||
|
const { LL } = useI18nContext();
|
||||||
|
const [deviceData, setDeviceData] = useState<DeviceData>({ label: '', data: [] });
|
||||||
|
const [selectedDeviceValue, setSelectedDeviceValue] = useState<DeviceValue>();
|
||||||
|
const [deviceDetails, setDeviceDetails] = useState<number>(-1);
|
||||||
|
const [onlyFav, setOnlyFav] = useState(false);
|
||||||
|
const [selectedDevice, setSelectedDevice] = useState<number>();
|
||||||
|
const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
const [coreData, setCoreData] = useState<CoreData>({
|
||||||
|
connected: true,
|
||||||
|
devices: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const common_theme = useTheme({
|
||||||
|
BaseRow: `
|
||||||
|
font-size: 14px;
|
||||||
|
`,
|
||||||
|
HeaderRow: `
|
||||||
|
text-transform: uppercase;
|
||||||
|
background-color: black;
|
||||||
|
color: #90CAF9;
|
||||||
|
.th {
|
||||||
|
border-bottom: 1px solid #565656;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Row: `
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
.td {
|
||||||
|
padding: 8px;
|
||||||
|
border-top: 1px solid #565656;
|
||||||
|
border-bottom: 1px solid #565656;
|
||||||
|
}
|
||||||
|
&.tr.tr-body.row-select.row-select-single-selected {
|
||||||
|
background-color: #3d4752;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
&:hover .td {
|
||||||
|
border-top: 1px solid #177ac9;
|
||||||
|
border-bottom: 1px solid #177ac9;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Cell: `
|
||||||
|
&:last-of-type {
|
||||||
|
text-align: right;
|
||||||
|
},
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
const device_theme = useTheme([
|
||||||
|
common_theme,
|
||||||
|
{
|
||||||
|
Table: `
|
||||||
|
--data-table-library_grid-template-columns: 40px 160px repeat(1, minmax(0, 1fr)) 100px 40px;
|
||||||
|
`,
|
||||||
|
BaseRow: `
|
||||||
|
.td {
|
||||||
|
height: 42px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
BaseCell: `
|
||||||
|
&:nth-of-type(2) {
|
||||||
|
text-align: left;
|
||||||
|
},
|
||||||
|
&:nth-of-type(4) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
HeaderRow: `
|
||||||
|
.th {
|
||||||
|
padding: 8px;
|
||||||
|
height: 36px;
|
||||||
|
`
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const data_theme = useTheme([
|
||||||
|
common_theme,
|
||||||
|
{
|
||||||
|
Table: `
|
||||||
|
--data-table-library_grid-template-columns: minmax(0, 1fr) 30% 40px;
|
||||||
|
`,
|
||||||
|
BaseRow: `
|
||||||
|
.td {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
BaseCell: `
|
||||||
|
&:nth-of-type(2) {
|
||||||
|
text-align: right;
|
||||||
|
},
|
||||||
|
`,
|
||||||
|
HeaderRow: `
|
||||||
|
.th {
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Row: `
|
||||||
|
&:nth-of-type(odd) .td {
|
||||||
|
background-color: #303030;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const getSortIcon = (state: any, sortKey: any) => {
|
||||||
|
if (state.sortKey === sortKey && state.reverse) {
|
||||||
|
return <KeyboardArrowDownOutlinedIcon />;
|
||||||
|
}
|
||||||
|
if (state.sortKey === sortKey && !state.reverse) {
|
||||||
|
return <KeyboardArrowUpOutlinedIcon />;
|
||||||
|
}
|
||||||
|
return <UnfoldMoreOutlinedIcon />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const dv_sort = useSort(
|
||||||
|
{ nodes: deviceData.data },
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
sortIcon: {
|
||||||
|
iconDefault: <UnfoldMoreOutlinedIcon />,
|
||||||
|
iconUp: <KeyboardArrowUpOutlinedIcon />,
|
||||||
|
iconDown: <KeyboardArrowDownOutlinedIcon />
|
||||||
|
},
|
||||||
|
sortToggleType: SortToggleType.AlternateWithReset,
|
||||||
|
sortFns: {
|
||||||
|
NAME: (array) => array.sort((a, b) => a.id.toString().slice(2).localeCompare(b.id.toString().slice(2))),
|
||||||
|
VALUE: (array) => array.sort((a, b) => a.v.toString().localeCompare(b.v.toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchDeviceData = async (id: number) => {
|
||||||
|
try {
|
||||||
|
setDeviceData((await EMSESP.readDeviceData({ id })).data);
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function onSelectChange(action: any, state: any) {
|
||||||
|
setSelectedDevice(state.id);
|
||||||
|
if (action.type === 'ADD_BY_ID_EXCLUSIVELY') {
|
||||||
|
void fetchDeviceData(state.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const device_select = useRowSelect(
|
||||||
|
{ nodes: coreData.devices },
|
||||||
|
{
|
||||||
|
onChange: onSelectChange
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchCoreData = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setSelectedDevice(undefined);
|
||||||
|
setCoreData((await EMSESP.readCoreData()).data);
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||||
|
}
|
||||||
|
}, [LL]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void fetchCoreData();
|
||||||
|
}, [fetchCoreData]);
|
||||||
|
|
||||||
|
const refreshData = () => {
|
||||||
|
if (deviceValueDialogOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedDevice) {
|
||||||
|
void fetchDeviceData(selectedDevice);
|
||||||
|
} else {
|
||||||
|
void fetchCoreData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const escapeCsvCell = (cell: any) => {
|
||||||
|
if (cell == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const sc = cell.toString().trim();
|
||||||
|
if (sc === '' || sc === '""') {
|
||||||
|
return sc;
|
||||||
|
}
|
||||||
|
if (sc.includes('"') || sc.includes(';') || sc.includes('\n') || sc.includes('\r')) {
|
||||||
|
return '"' + sc.replace(/"/g, '""') + '"';
|
||||||
|
}
|
||||||
|
return sc;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeCsvData = (columns: any, data: any) =>
|
||||||
|
data.reduce(
|
||||||
|
(csvString: any, rowItem: any) =>
|
||||||
|
csvString + columns.map(({ accessor }: any) => escapeCsvCell(accessor(rowItem))).join(';') + '\r\n',
|
||||||
|
columns.map(({ name }: any) => escapeCsvCell(name)).join(';') + '\r\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
const downloadAsCsv = (columns: any, data: any, filename: string) => {
|
||||||
|
const csvData = makeCsvData(columns, data);
|
||||||
|
const csvFile = new Blob([csvData], { type: 'text/csv;charset:utf-8' });
|
||||||
|
const downloadLink = document.createElement('a');
|
||||||
|
|
||||||
|
downloadLink.download = filename;
|
||||||
|
downloadLink.href = window.URL.createObjectURL(csvFile);
|
||||||
|
document.body.appendChild(downloadLink);
|
||||||
|
downloadLink.click();
|
||||||
|
document.body.removeChild(downloadLink);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasMask = (id: string, mask: number) => (parseInt(id.slice(0, 2), 16) & mask) === mask;
|
||||||
|
|
||||||
|
const handleDownloadCsv = () => {
|
||||||
|
const columns = [
|
||||||
|
{ accessor: (dv: any) => dv.id.slice(2), name: LL.ENTITY_NAME() },
|
||||||
|
{
|
||||||
|
accessor: (dv: any) => (typeof dv.v === 'number' ? new Intl.NumberFormat().format(dv.v) : dv.v),
|
||||||
|
name: LL.VALUE(0)
|
||||||
|
},
|
||||||
|
{ accessor: (dv: any) => DeviceValueUOM_s[dv.u], name: 'UoM' }
|
||||||
|
];
|
||||||
|
downloadAsCsv(
|
||||||
|
columns,
|
||||||
|
onlyFav ? deviceData.data.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE)) : deviceData.data,
|
||||||
|
'device_entities'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setInterval(() => refreshData(), 60000);
|
||||||
|
return () => {
|
||||||
|
clearInterval(timer);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const deviceValueDialogSave = async (dv: DeviceValue) => {
|
||||||
|
const selectedDeviceID = Number(device_select.state.id);
|
||||||
|
try {
|
||||||
|
const response = await EMSESP.writeDeviceValue({
|
||||||
|
id: selectedDeviceID,
|
||||||
|
devicevalue: dv
|
||||||
|
});
|
||||||
|
if (response.status === 204) {
|
||||||
|
toast.error(LL.WRITE_CMD_FAILED());
|
||||||
|
} else if (response.status === 403) {
|
||||||
|
toast.error(LL.ACCESS_DENIED());
|
||||||
|
} else {
|
||||||
|
toast.success(LL.WRITE_CMD_SENT());
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
||||||
|
} finally {
|
||||||
|
setDeviceValueDialogOpen(false);
|
||||||
|
await fetchDeviceData(selectedDeviceID);
|
||||||
|
setSelectedDeviceValue(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderDeviceDetails = () => {
|
||||||
|
if (coreData && coreData.devices.length > 0 && deviceDetails !== -1) {
|
||||||
|
const device = coreData.devices[deviceDetails];
|
||||||
|
return (
|
||||||
|
<Dialog open={deviceDetails !== -1} onClose={() => setDeviceDetails(-1)}>
|
||||||
|
<DialogTitle>{LL.DEVICE_DETAILS()}</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<List dense={true}>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText primary={LL.TYPE(0)} secondary={device.tn} />
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText primary={LL.NAME(0)} secondary={device.n} />
|
||||||
|
</ListItem>
|
||||||
|
{device.t !== DeviceType.CUSTOM && (
|
||||||
|
<>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText primary={LL.BRAND()} secondary={device.b} />
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText
|
||||||
|
primary={LL.ID_OF(LL.DEVICE())}
|
||||||
|
secondary={'0x' + ('00' + device.d.toString(16).toUpperCase()).slice(-2)}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText primary={LL.ID_OF(LL.PRODUCT())} secondary={device.p} />
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText primary={LL.VERSION()} secondary={device.v} />
|
||||||
|
</ListItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant="outlined" onClick={() => setDeviceDetails(-1)} color="secondary">
|
||||||
|
{LL.CLOSE()}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCoreData = () => (
|
||||||
|
<IconContext.Provider value={{ color: 'lightblue', size: '24', style: { verticalAlign: 'middle' } }}>
|
||||||
|
{!coreData.connected && <MessageBox my={2} level="error" message={LL.EMS_BUS_WARNING()} />}
|
||||||
|
{coreData.connected && coreData.devices.length === 0 && (
|
||||||
|
<MessageBox my={2} level="warning" message={LL.EMS_BUS_SCANNING()} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Table
|
||||||
|
data={{ nodes: selectedDevice ? coreData.devices.filter((dv) => dv.id === selectedDevice) : coreData.devices }}
|
||||||
|
select={device_select}
|
||||||
|
theme={device_theme}
|
||||||
|
layout={{ custom: true }}
|
||||||
|
>
|
||||||
|
{(tableList: any) => (
|
||||||
|
<>
|
||||||
|
<Header>
|
||||||
|
<HeaderRow>
|
||||||
|
<HeaderCell stiff />
|
||||||
|
<HeaderCell stiff>{LL.TYPE(0)}</HeaderCell>
|
||||||
|
<HeaderCell resize>{LL.DESCRIPTION()}</HeaderCell>
|
||||||
|
<HeaderCell stiff>{LL.ENTITIES()}</HeaderCell>
|
||||||
|
<HeaderCell stiff />
|
||||||
|
</HeaderRow>
|
||||||
|
</Header>
|
||||||
|
<Body>
|
||||||
|
{tableList.map((device: Device, index: number) => (
|
||||||
|
<Row key={device.id} item={device}>
|
||||||
|
<Cell stiff>
|
||||||
|
<DeviceIcon type_id={device.t} />
|
||||||
|
</Cell>
|
||||||
|
<Cell stiff>{device.tn}</Cell>
|
||||||
|
<Cell>{device.n}</Cell>
|
||||||
|
<Cell stiff>{device.e}</Cell>
|
||||||
|
<Cell stiff>
|
||||||
|
<IconButton size="small" onClick={() => setDeviceDetails(index)}>
|
||||||
|
<InfoOutlinedIcon color="info" sx={{ fontSize: 16, verticalAlign: 'middle' }} />
|
||||||
|
</IconButton>
|
||||||
|
</Cell>
|
||||||
|
</Row>
|
||||||
|
))}
|
||||||
|
</Body>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
</IconContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const deviceValueDialogClose = () => {
|
||||||
|
setDeviceValueDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderDeviceData = () => {
|
||||||
|
if (!selectedDevice) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendCommand = (dv: DeviceValue) => {
|
||||||
|
if (dv.c && me.admin && !hasMask(dv.id, DeviceEntityMask.DV_READONLY)) {
|
||||||
|
setSelectedDeviceValue(dv);
|
||||||
|
setDeviceValueDialogOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderNameCell = (dv: DeviceValue) => (
|
||||||
|
<>
|
||||||
|
{dv.id.slice(2)}
|
||||||
|
{hasMask(dv.id, DeviceEntityMask.DV_FAVORITE) && <StarIcon color="primary" sx={{ fontSize: 12 }} />}
|
||||||
|
{hasMask(dv.id, DeviceEntityMask.DV_READONLY) && <EditOffOutlinedIcon color="primary" sx={{ fontSize: 12 }} />}
|
||||||
|
{hasMask(dv.id, DeviceEntityMask.DV_API_MQTT_EXCLUDE) && (
|
||||||
|
<CommentsDisabledOutlinedIcon color="primary" sx={{ fontSize: 12 }} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: 'black',
|
||||||
|
overflowY: 'scroll',
|
||||||
|
position: 'absolute',
|
||||||
|
right: 18,
|
||||||
|
bottom: 18,
|
||||||
|
left: () => leftOffset(),
|
||||||
|
top: () => topOffset()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Checkbox size="small" name="onlyFav" checked={onlyFav} onChange={() => setOnlyFav(!onlyFav)} />}
|
||||||
|
label={
|
||||||
|
<span style={{ fontSize: '12px' }}>
|
||||||
|
{LL.SHOW_FAV()} (
|
||||||
|
<StarIcon color="primary" sx={{ fontSize: 12 }} />)
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
labelPlacement="start"
|
||||||
|
/>
|
||||||
|
<Table
|
||||||
|
data={{
|
||||||
|
nodes: onlyFav
|
||||||
|
? deviceData.data.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE))
|
||||||
|
: deviceData.data
|
||||||
|
}}
|
||||||
|
theme={data_theme}
|
||||||
|
sort={dv_sort}
|
||||||
|
layout={{ custom: true }}
|
||||||
|
>
|
||||||
|
{(tableList: any) => (
|
||||||
|
<>
|
||||||
|
<Header>
|
||||||
|
<HeaderRow>
|
||||||
|
<HeaderCell resize>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
|
||||||
|
endIcon={getSortIcon(dv_sort.state, 'NAME')}
|
||||||
|
onClick={() => dv_sort.fns.onToggleSort({ sortKey: 'NAME' })}
|
||||||
|
>
|
||||||
|
{LL.ENTITY_NAME()}
|
||||||
|
</Button>
|
||||||
|
</HeaderCell>
|
||||||
|
<HeaderCell resize>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
style={{ fontSize: '14px', justifyContent: 'flex-end' }}
|
||||||
|
endIcon={getSortIcon(dv_sort.state, 'VALUE')}
|
||||||
|
onClick={() => dv_sort.fns.onToggleSort({ sortKey: 'VALUE' })}
|
||||||
|
>
|
||||||
|
{LL.VALUE(0)}
|
||||||
|
</Button>
|
||||||
|
</HeaderCell>
|
||||||
|
<HeaderCell stiff />
|
||||||
|
</HeaderRow>
|
||||||
|
</Header>
|
||||||
|
<Body>
|
||||||
|
{tableList.map((dv: DeviceValue) => (
|
||||||
|
<Row key={dv.id} item={dv} onClick={() => sendCommand(dv)}>
|
||||||
|
<Cell>{renderNameCell(dv)}</Cell>
|
||||||
|
<Cell>{formatValue(LL, dv.v, dv.u)}</Cell>
|
||||||
|
<Cell stiff>
|
||||||
|
{dv.c && me.admin && !hasMask(dv.id, DeviceEntityMask.DV_READONLY) && (
|
||||||
|
<IconButton size="small" onClick={() => sendCommand(dv)}>
|
||||||
|
{dv.v === '' && dv.c ? (
|
||||||
|
<PlayArrowIcon color="primary" sx={{ fontSize: 16 }} />
|
||||||
|
) : (
|
||||||
|
<EditIcon color="primary" sx={{ fontSize: 16 }} />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</Cell>
|
||||||
|
</Row>
|
||||||
|
))}
|
||||||
|
</Body>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SectionContent title={LL.DEVICE_DATA()} titleGutter id="devices-window">
|
||||||
|
{renderCoreData()}
|
||||||
|
{renderDeviceData()}
|
||||||
|
{renderDeviceDetails()}
|
||||||
|
|
||||||
|
{selectedDeviceValue && (
|
||||||
|
<DashboarDevicesDialog
|
||||||
|
open={deviceValueDialogOpen}
|
||||||
|
onClose={deviceValueDialogClose}
|
||||||
|
onSave={deviceValueDialogSave}
|
||||||
|
selectedItem={selectedDeviceValue}
|
||||||
|
validator={deviceValueItemValidation(selectedDeviceValue)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box display="flex" flexWrap="wrap">
|
||||||
|
<Box flexGrow={1}>
|
||||||
|
<ButtonRow>
|
||||||
|
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={refreshData}>
|
||||||
|
{LL.REFRESH()}
|
||||||
|
</Button>
|
||||||
|
{device_select.state.id && device_select.state.id !== 'sensor' && (
|
||||||
|
<Button startIcon={<DownloadIcon />} variant="outlined" onClick={handleDownloadCsv}>
|
||||||
|
{LL.EXPORT()}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ButtonRow>
|
||||||
|
</Box>
|
||||||
|
<Box flexWrap="nowrap" whiteSpace="nowrap">
|
||||||
|
<ButtonRow>
|
||||||
|
<Button
|
||||||
|
startIcon={<SettingsBackupRestoreIcon />}
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => device_select.fns.onRemoveAll()}
|
||||||
|
color="warning"
|
||||||
|
>
|
||||||
|
{LL.RESET(0)}
|
||||||
|
</Button>
|
||||||
|
</ButtonRow>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</SectionContent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardDevices;
|
||||||
174
interface/src/project/DashboardDevicesDialog.tsx
Normal file
174
interface/src/project/DashboardDevicesDialog.tsx
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
InputAdornment,
|
||||||
|
MenuItem,
|
||||||
|
TextField,
|
||||||
|
FormHelperText,
|
||||||
|
Grid,
|
||||||
|
Box,
|
||||||
|
Typography
|
||||||
|
} from '@mui/material';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
|
||||||
|
import type { DeviceValue } from './types';
|
||||||
|
import type Schema from 'async-validator';
|
||||||
|
|
||||||
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
|
import { ValidatedTextField } from 'components';
|
||||||
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { updateValue } from 'utils';
|
||||||
|
|
||||||
|
import { validate } from 'validators';
|
||||||
|
|
||||||
|
type DashboardDevicesDialogProps = {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSave: (as: DeviceValue) => void;
|
||||||
|
selectedItem: DeviceValue;
|
||||||
|
validator: Schema;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DashboarDevicesDialog = ({ open, onClose, onSave, selectedItem, validator }: DashboardDevicesDialogProps) => {
|
||||||
|
const { LL } = useI18nContext();
|
||||||
|
const [editItem, setEditItem] = useState<DeviceValue>(selectedItem);
|
||||||
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
|
||||||
|
const updateFormValue = updateValue(setEditItem);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
setFieldErrors(undefined);
|
||||||
|
setEditItem(selectedItem);
|
||||||
|
}
|
||||||
|
}, [open, selectedItem]);
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
try {
|
||||||
|
setFieldErrors(undefined);
|
||||||
|
await validate(validator, editItem);
|
||||||
|
onSave(editItem);
|
||||||
|
} catch (errors: any) {
|
||||||
|
setFieldErrors(errors);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setUom = (uom: number) => {
|
||||||
|
switch (uom) {
|
||||||
|
case DeviceValueUOM.HOURS:
|
||||||
|
return LL.HOURS();
|
||||||
|
case DeviceValueUOM.MINUTES:
|
||||||
|
return LL.MINUTES();
|
||||||
|
case DeviceValueUOM.SECONDS:
|
||||||
|
return LL.SECONDS();
|
||||||
|
default:
|
||||||
|
return DeviceValueUOM_s[uom];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const showHelperText = (dv: DeviceValue) => {
|
||||||
|
if (dv.h) {
|
||||||
|
return dv.h;
|
||||||
|
}
|
||||||
|
if (dv.l) {
|
||||||
|
return '[ ' + dv.l.join(' | ') + ' ]';
|
||||||
|
}
|
||||||
|
|
||||||
|
let helperText = '<';
|
||||||
|
if (dv.u !== DeviceValueUOM.NONE) {
|
||||||
|
helperText += 'n';
|
||||||
|
if (dv.m && dv.x) {
|
||||||
|
helperText += ' between ' + dv.m + ' and ' + dv.x;
|
||||||
|
}
|
||||||
|
if (dv.s) {
|
||||||
|
helperText += ' , step ' + dv.s;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
helperText += 'text';
|
||||||
|
}
|
||||||
|
return helperText + '>';
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={close}>
|
||||||
|
<DialogTitle>{selectedItem.v === '' && selectedItem.c ? LL.RUN_COMMAND() : LL.CHANGE_VALUE()}</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
|
||||||
|
<Typography variant="body2">{editItem.id.slice(2)}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Grid>
|
||||||
|
<Grid item>
|
||||||
|
{editItem.l ? (
|
||||||
|
<TextField
|
||||||
|
name="v"
|
||||||
|
label={LL.VALUE(1)}
|
||||||
|
value={editItem.v}
|
||||||
|
autoFocus
|
||||||
|
sx={{ width: '30ch' }}
|
||||||
|
select
|
||||||
|
onChange={updateFormValue}
|
||||||
|
>
|
||||||
|
{editItem.l.map((val) => (
|
||||||
|
<MenuItem value={val} key={val}>
|
||||||
|
{val}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
) : editItem.u !== DeviceValueUOM.NONE ? (
|
||||||
|
<ValidatedTextField
|
||||||
|
fieldErrors={fieldErrors}
|
||||||
|
name="v"
|
||||||
|
label={LL.VALUE(1)}
|
||||||
|
value={Math.round(editItem.v * 10) / 10}
|
||||||
|
autoFocus
|
||||||
|
type="number"
|
||||||
|
sx={{ width: '30ch' }}
|
||||||
|
onChange={updateFormValue}
|
||||||
|
inputProps={editItem.u ? { min: editItem.m, max: editItem.x, step: editItem.s } : {}}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: <InputAdornment position="start">{setUom(editItem.u)}</InputAdornment>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ValidatedTextField
|
||||||
|
fieldErrors={fieldErrors}
|
||||||
|
name="v"
|
||||||
|
label={LL.VALUE(1)}
|
||||||
|
value={editItem.v}
|
||||||
|
autoFocus
|
||||||
|
sx={{ width: '30ch' }}
|
||||||
|
multiline={editItem.u ? false : true}
|
||||||
|
onChange={updateFormValue}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<FormHelperText>format: {showHelperText(editItem)}</FormHelperText>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
<DialogActions>
|
||||||
|
<Button startIcon={<CancelIcon />} variant="outlined" onClick={close} color="secondary">
|
||||||
|
{LL.CANCEL()}
|
||||||
|
</Button>
|
||||||
|
<Button startIcon={<WarningIcon color="warning" />} variant="contained" onClick={save} color="info">
|
||||||
|
{LL.UPDATE()}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboarDevicesDialog;
|
||||||
471
interface/src/project/DashboardSensors.tsx
Normal file
471
interface/src/project/DashboardSensors.tsx
Normal file
@@ -0,0 +1,471 @@
|
|||||||
|
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
|
||||||
|
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
|
||||||
|
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
|
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
|
||||||
|
import { Button, Typography, Box } from '@mui/material';
|
||||||
|
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
|
||||||
|
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||||
|
import { useTheme } from '@table-library/react-table-library/theme';
|
||||||
|
import { useState, useContext, useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import DashboardSensorsAnalogDialog from './DashboardSensorsAnalogDialog';
|
||||||
|
import DashboardSensorsTemperatureDialog from './DashboardSensorsTemperatureDialog';
|
||||||
|
import * as EMSESP from './api';
|
||||||
|
|
||||||
|
import { DeviceValueUOM, DeviceValueUOM_s, AnalogTypeNames } from './types';
|
||||||
|
import { temperatureSensorItemValidation, analogSensorItemValidation } from './validators';
|
||||||
|
import type { SensorData, TemperatureSensor, AnalogSensor } from './types';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
import { ButtonRow, SectionContent } from 'components';
|
||||||
|
|
||||||
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { extractErrorMessage } from 'utils';
|
||||||
|
|
||||||
|
const DashboardSensors: FC = () => {
|
||||||
|
const { LL } = useI18nContext();
|
||||||
|
const { me } = useContext(AuthenticatedContext);
|
||||||
|
const [sensorData, setSensorData] = useState<SensorData>({ ts: [], as: [], analog_enabled: false });
|
||||||
|
const [selectedTemperatureSensor, setSelectedTemperatureSensor] = useState<TemperatureSensor>();
|
||||||
|
const [selectedAnalogSensor, setSelectedAnalogSensor] = useState<AnalogSensor>();
|
||||||
|
const [temperatureDialogOpen, setTemperatureDialogOpen] = useState<boolean>(false);
|
||||||
|
const [analogDialogOpen, setAnalogDialogOpen] = useState<boolean>(false);
|
||||||
|
const [creating, setCreating] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const isAdmin = me.admin;
|
||||||
|
|
||||||
|
const common_theme = useTheme({
|
||||||
|
BaseRow: `
|
||||||
|
font-size: 14px;
|
||||||
|
.td {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
HeaderRow: `
|
||||||
|
text-transform: uppercase;
|
||||||
|
background-color: black;
|
||||||
|
color: #90CAF9;
|
||||||
|
.th {
|
||||||
|
border-bottom: 1px solid #565656;
|
||||||
|
}
|
||||||
|
.th {
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Row: `
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
.td {
|
||||||
|
padding: 8px;
|
||||||
|
border-top: 1px solid #565656;
|
||||||
|
border-bottom: 1px solid #565656;
|
||||||
|
}
|
||||||
|
&.tr.tr-body.row-select.row-select-single-selected {
|
||||||
|
background-color: #3d4752;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
&:hover .td {
|
||||||
|
border-top: 1px solid #177ac9;
|
||||||
|
border-bottom: 1px solid #177ac9;
|
||||||
|
}
|
||||||
|
&:nth-of-type(odd) .td {
|
||||||
|
background-color: #303030;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Cell: `
|
||||||
|
&:last-of-type {
|
||||||
|
text-align: right;
|
||||||
|
},
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
const temperature_theme = useTheme([
|
||||||
|
common_theme,
|
||||||
|
{
|
||||||
|
Table: `
|
||||||
|
--data-table-library_grid-template-columns: minmax(0, 1fr) 35%;
|
||||||
|
`
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const analog_theme = useTheme([
|
||||||
|
common_theme,
|
||||||
|
{
|
||||||
|
Table: `
|
||||||
|
--data-table-library_grid-template-columns: 80px repeat(1, minmax(0, 1fr)) 120px 100px;
|
||||||
|
`
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const fetchSensorData = useCallback(async () => {
|
||||||
|
if (!analogDialogOpen && !temperatureDialogOpen) {
|
||||||
|
try {
|
||||||
|
setSensorData((await EMSESP.readSensorData()).data);
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [LL, analogDialogOpen, temperatureDialogOpen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void fetchSensorData();
|
||||||
|
}, [fetchSensorData]);
|
||||||
|
|
||||||
|
const getSortIcon = (state: any, sortKey: any) => {
|
||||||
|
if (state.sortKey === sortKey && state.reverse) {
|
||||||
|
return <KeyboardArrowDownOutlinedIcon />;
|
||||||
|
}
|
||||||
|
if (state.sortKey === sortKey && !state.reverse) {
|
||||||
|
return <KeyboardArrowUpOutlinedIcon />;
|
||||||
|
}
|
||||||
|
return <UnfoldMoreOutlinedIcon />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const analog_sort = useSort(
|
||||||
|
{ nodes: sensorData.as },
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
sortIcon: {
|
||||||
|
iconDefault: <UnfoldMoreOutlinedIcon />,
|
||||||
|
iconUp: <KeyboardArrowUpOutlinedIcon />,
|
||||||
|
iconDown: <KeyboardArrowDownOutlinedIcon />
|
||||||
|
},
|
||||||
|
sortToggleType: SortToggleType.AlternateWithReset,
|
||||||
|
sortFns: {
|
||||||
|
GPIO: (array) => array.sort((a, b) => a.g - b.g),
|
||||||
|
NAME: (array) => array.sort((a, b) => a.n.localeCompare(b.n)),
|
||||||
|
TYPE: (array) => array.sort((a, b) => a.t - b.t),
|
||||||
|
VALUE: (array) => array.sort((a, b) => a.v - b.v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const temperature_sort = useSort(
|
||||||
|
{ nodes: sensorData.ts },
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
sortIcon: {
|
||||||
|
iconDefault: <UnfoldMoreOutlinedIcon />,
|
||||||
|
iconUp: <KeyboardArrowUpOutlinedIcon />,
|
||||||
|
iconDown: <KeyboardArrowDownOutlinedIcon />
|
||||||
|
},
|
||||||
|
sortToggleType: SortToggleType.AlternateWithReset,
|
||||||
|
sortFns: {
|
||||||
|
NAME: (array) => array.sort((a, b) => a.n.localeCompare(b.n)),
|
||||||
|
VALUE: (array) => array.sort((a, b) => a.t - b.t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setInterval(() => fetchSensorData(), 30000);
|
||||||
|
return () => {
|
||||||
|
clearInterval(timer);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const formatDurationMin = (duration_min: number) => {
|
||||||
|
const days = Math.trunc((duration_min * 60000) / 86400000);
|
||||||
|
const hours = Math.trunc((duration_min * 60000) / 3600000) % 24;
|
||||||
|
const minutes = Math.trunc((duration_min * 60000) / 60000) % 60;
|
||||||
|
|
||||||
|
let formatted = '';
|
||||||
|
if (days) {
|
||||||
|
formatted += LL.NUM_DAYS({ num: days }) + ' ';
|
||||||
|
}
|
||||||
|
if (hours) {
|
||||||
|
formatted += LL.NUM_HOURS({ num: hours }) + ' ';
|
||||||
|
}
|
||||||
|
if (minutes) {
|
||||||
|
formatted += LL.NUM_MINUTES({ num: minutes });
|
||||||
|
}
|
||||||
|
return formatted;
|
||||||
|
};
|
||||||
|
|
||||||
|
function formatValue(value: any, uom: number) {
|
||||||
|
if (value === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
switch (uom) {
|
||||||
|
case DeviceValueUOM.HOURS:
|
||||||
|
return value ? formatDurationMin(value * 60) : LL.NUM_HOURS({ num: 0 });
|
||||||
|
case DeviceValueUOM.MINUTES:
|
||||||
|
return value ? formatDurationMin(value) : LL.NUM_MINUTES({ num: 0 });
|
||||||
|
case DeviceValueUOM.SECONDS:
|
||||||
|
return LL.NUM_SECONDS({ num: value });
|
||||||
|
case DeviceValueUOM.NONE:
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return new Intl.NumberFormat().format(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
case DeviceValueUOM.DEGREES:
|
||||||
|
case DeviceValueUOM.DEGREES_R:
|
||||||
|
case DeviceValueUOM.FAHRENHEIT:
|
||||||
|
return (
|
||||||
|
new Intl.NumberFormat(undefined, {
|
||||||
|
minimumFractionDigits: 1
|
||||||
|
}).format(value) +
|
||||||
|
' ' +
|
||||||
|
DeviceValueUOM_s[uom]
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return new Intl.NumberFormat().format(value) + ' ' + DeviceValueUOM_s[uom];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateTemperatureSensor = (ts: TemperatureSensor) => {
|
||||||
|
if (isAdmin) {
|
||||||
|
setSelectedTemperatureSensor(ts);
|
||||||
|
setTemperatureDialogOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTemperatureDialogClose = () => {
|
||||||
|
setTemperatureDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTemperatureDialogSave = async (ts: TemperatureSensor) => {
|
||||||
|
try {
|
||||||
|
const response = await EMSESP.writeTemperatureSensor({
|
||||||
|
id: ts.id,
|
||||||
|
name: ts.n,
|
||||||
|
offset: ts.o
|
||||||
|
});
|
||||||
|
if (response.status === 204) {
|
||||||
|
toast.error(LL.UPLOAD_OF(LL.SENSOR()) + ' ' + LL.FAILED());
|
||||||
|
} else if (response.status === 403) {
|
||||||
|
toast.error(LL.ACCESS_DENIED());
|
||||||
|
} else {
|
||||||
|
toast.success(LL.UPDATED_OF(LL.SENSOR()));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
||||||
|
} finally {
|
||||||
|
setTemperatureDialogOpen(false);
|
||||||
|
setSelectedTemperatureSensor(undefined);
|
||||||
|
await fetchSensorData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateAnalogSensor = (as: AnalogSensor) => {
|
||||||
|
if (isAdmin) {
|
||||||
|
setCreating(false);
|
||||||
|
setSelectedAnalogSensor(as);
|
||||||
|
setAnalogDialogOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAnalogDialogClose = () => {
|
||||||
|
setAnalogDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addAnalogSensor = () => {
|
||||||
|
setCreating(true);
|
||||||
|
setSelectedAnalogSensor({
|
||||||
|
id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100),
|
||||||
|
n: '',
|
||||||
|
g: 40,
|
||||||
|
u: 0,
|
||||||
|
v: 0,
|
||||||
|
o: 0,
|
||||||
|
t: 0,
|
||||||
|
f: 1,
|
||||||
|
d: false
|
||||||
|
});
|
||||||
|
setAnalogDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAnalogDialogSave = async (as: AnalogSensor) => {
|
||||||
|
try {
|
||||||
|
const response = await EMSESP.writeAnalogSensor({
|
||||||
|
id: as.id,
|
||||||
|
gpio: as.g,
|
||||||
|
name: as.n,
|
||||||
|
offset: as.o,
|
||||||
|
factor: as.f,
|
||||||
|
uom: as.u,
|
||||||
|
type: as.t,
|
||||||
|
deleted: as.d
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 204) {
|
||||||
|
toast.error(LL.UPDATE_OF(LL.ANALOG_SENSOR()) + ' ' + LL.FAILED());
|
||||||
|
} else if (response.status === 403) {
|
||||||
|
toast.error(LL.ACCESS_DENIED());
|
||||||
|
} else {
|
||||||
|
toast.success(LL.UPDATED_OF(LL.ANALOG_SENSOR()));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
||||||
|
} finally {
|
||||||
|
setAnalogDialogOpen(false);
|
||||||
|
setSelectedAnalogSensor(undefined);
|
||||||
|
await fetchSensorData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const RenderTemperatureSensors = () => (
|
||||||
|
<Table data={{ nodes: sensorData.ts }} theme={temperature_theme} sort={temperature_sort} layout={{ custom: true }}>
|
||||||
|
{(tableList: any) => (
|
||||||
|
<>
|
||||||
|
<Header>
|
||||||
|
<HeaderRow>
|
||||||
|
<HeaderCell resize>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
|
||||||
|
endIcon={getSortIcon(temperature_sort.state, 'NAME')}
|
||||||
|
onClick={() => temperature_sort.fns.onToggleSort({ sortKey: 'NAME' })}
|
||||||
|
>
|
||||||
|
{LL.NAME(0)}
|
||||||
|
</Button>
|
||||||
|
</HeaderCell>
|
||||||
|
<HeaderCell stiff>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
style={{ fontSize: '14px', justifyContent: 'flex-end' }}
|
||||||
|
endIcon={getSortIcon(temperature_sort.state, 'VALUE')}
|
||||||
|
onClick={() => temperature_sort.fns.onToggleSort({ sortKey: 'VALUE' })}
|
||||||
|
>
|
||||||
|
{LL.VALUE(0)}
|
||||||
|
</Button>
|
||||||
|
</HeaderCell>
|
||||||
|
</HeaderRow>
|
||||||
|
</Header>
|
||||||
|
<Body>
|
||||||
|
{tableList.map((ts: TemperatureSensor) => (
|
||||||
|
<Row key={ts.id} item={ts} onClick={() => updateTemperatureSensor(ts)}>
|
||||||
|
<Cell>{ts.n}</Cell>
|
||||||
|
<Cell>{formatValue(ts.t, ts.u)}</Cell>
|
||||||
|
</Row>
|
||||||
|
))}
|
||||||
|
</Body>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
|
||||||
|
const RenderAnalogSensors = () => (
|
||||||
|
<Table data={{ nodes: sensorData.as }} theme={analog_theme} sort={analog_sort} layout={{ custom: true }}>
|
||||||
|
{(tableList: any) => (
|
||||||
|
<>
|
||||||
|
<Header>
|
||||||
|
<HeaderRow>
|
||||||
|
<HeaderCell stiff>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
|
||||||
|
endIcon={getSortIcon(analog_sort.state, 'GPIO')}
|
||||||
|
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'GPIO' })}
|
||||||
|
>
|
||||||
|
GPIO
|
||||||
|
</Button>
|
||||||
|
</HeaderCell>
|
||||||
|
<HeaderCell resize>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
|
||||||
|
endIcon={getSortIcon(analog_sort.state, 'NAME')}
|
||||||
|
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'NAME' })}
|
||||||
|
>
|
||||||
|
{LL.NAME(0)}
|
||||||
|
</Button>
|
||||||
|
</HeaderCell>
|
||||||
|
<HeaderCell stiff>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
|
||||||
|
endIcon={getSortIcon(analog_sort.state, 'TYPE')}
|
||||||
|
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'TYPE' })}
|
||||||
|
>
|
||||||
|
{LL.TYPE(0)}
|
||||||
|
</Button>
|
||||||
|
</HeaderCell>
|
||||||
|
<HeaderCell stiff>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
style={{ fontSize: '14px', justifyContent: 'flex-end' }}
|
||||||
|
endIcon={getSortIcon(analog_sort.state, 'VALUE')}
|
||||||
|
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'VALUE' })}
|
||||||
|
>
|
||||||
|
{LL.VALUE(0)}
|
||||||
|
</Button>
|
||||||
|
</HeaderCell>
|
||||||
|
</HeaderRow>
|
||||||
|
</Header>
|
||||||
|
<Body>
|
||||||
|
{tableList.map((a: AnalogSensor) => (
|
||||||
|
<Row key={a.id} item={a} onClick={() => updateAnalogSensor(a)}>
|
||||||
|
<Cell stiff>{a.g}</Cell>
|
||||||
|
<Cell>{a.n}</Cell>
|
||||||
|
<Cell stiff>{AnalogTypeNames[a.t]} </Cell>
|
||||||
|
<Cell stiff>{a.t ? formatValue(a.v, a.u) : ''}</Cell>
|
||||||
|
</Row>
|
||||||
|
))}
|
||||||
|
</Body>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SectionContent title={LL.SENSOR_DATA()} titleGutter>
|
||||||
|
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary">
|
||||||
|
{LL.TEMP_SENSORS()}
|
||||||
|
</Typography>
|
||||||
|
<RenderTemperatureSensors />
|
||||||
|
{selectedTemperatureSensor && (
|
||||||
|
<DashboardSensorsTemperatureDialog
|
||||||
|
open={temperatureDialogOpen}
|
||||||
|
onClose={onTemperatureDialogClose}
|
||||||
|
onSave={onTemperatureDialogSave}
|
||||||
|
selectedItem={selectedTemperatureSensor}
|
||||||
|
validator={temperatureSensorItemValidation()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{sensorData?.analog_enabled === true && (
|
||||||
|
<>
|
||||||
|
<Typography sx={{ pt: 4, pb: 1 }} variant="h6" color="secondary">
|
||||||
|
{LL.ANALOG_SENSORS()}
|
||||||
|
</Typography>
|
||||||
|
<RenderAnalogSensors />
|
||||||
|
{selectedAnalogSensor && (
|
||||||
|
<DashboardSensorsAnalogDialog
|
||||||
|
open={analogDialogOpen}
|
||||||
|
onClose={onAnalogDialogClose}
|
||||||
|
onSave={onAnalogDialogSave}
|
||||||
|
creating={creating}
|
||||||
|
selectedItem={selectedAnalogSensor}
|
||||||
|
validator={analogSensorItemValidation(sensorData.as, creating)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ButtonRow>
|
||||||
|
<Box mt={2} display="flex" flexWrap="wrap">
|
||||||
|
<Box flexGrow={1}>
|
||||||
|
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={fetchSensorData}>
|
||||||
|
{LL.REFRESH()}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<AddCircleOutlineOutlinedIcon />}
|
||||||
|
onClick={addAnalogSensor}
|
||||||
|
>
|
||||||
|
{LL.ADD(0) + ' ' + LL.ANALOG_SENSOR()}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</ButtonRow>
|
||||||
|
</SectionContent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardSensors;
|
||||||
264
interface/src/project/DashboardSensorsAnalogDialog.tsx
Normal file
264
interface/src/project/DashboardSensorsAnalogDialog.tsx
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
Box,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
InputAdornment,
|
||||||
|
Grid,
|
||||||
|
MenuItem,
|
||||||
|
TextField
|
||||||
|
} from '@mui/material';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
import { AnalogType, AnalogTypeNames, DeviceValueUOM_s } from './types';
|
||||||
|
import type { AnalogSensor } from './types';
|
||||||
|
import type Schema from 'async-validator';
|
||||||
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
|
import { ValidatedTextField } from 'components';
|
||||||
|
|
||||||
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { numberValue, updateValue } from 'utils';
|
||||||
|
|
||||||
|
import { validate } from 'validators';
|
||||||
|
|
||||||
|
type DashboardSensorsAnalogDialogProps = {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSave: (as: AnalogSensor) => void;
|
||||||
|
creating: boolean;
|
||||||
|
selectedItem: AnalogSensor;
|
||||||
|
validator: Schema;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DashboardSensorsAnalogDialog = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSave,
|
||||||
|
creating,
|
||||||
|
selectedItem,
|
||||||
|
validator
|
||||||
|
}: DashboardSensorsAnalogDialogProps) => {
|
||||||
|
const { LL } = useI18nContext();
|
||||||
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
const [editItem, setEditItem] = useState<AnalogSensor>(selectedItem);
|
||||||
|
const updateFormValue = updateValue(setEditItem);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
setFieldErrors(undefined);
|
||||||
|
setEditItem(selectedItem);
|
||||||
|
}
|
||||||
|
}, [open, selectedItem]);
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
try {
|
||||||
|
setFieldErrors(undefined);
|
||||||
|
await validate(validator, editItem);
|
||||||
|
onSave(editItem);
|
||||||
|
} catch (errors: any) {
|
||||||
|
setFieldErrors(errors);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const remove = () => {
|
||||||
|
editItem.d = true;
|
||||||
|
onSave(editItem);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={close}>
|
||||||
|
<DialogTitle>
|
||||||
|
{creating ? LL.ADD(1) + ' ' + LL.NEW(1) : LL.EDIT()} {LL.ANALOG_SENSOR()}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<ValidatedTextField
|
||||||
|
fieldErrors={fieldErrors}
|
||||||
|
name="g"
|
||||||
|
label="GPIO"
|
||||||
|
disabled={!creating}
|
||||||
|
value={numberValue(editItem.g)}
|
||||||
|
type="number"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
{creating && (
|
||||||
|
<Grid item>
|
||||||
|
<Box color="warning.main" mt={2}>
|
||||||
|
<Typography variant="body2">{LL.WARN_GPIO()}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<ValidatedTextField
|
||||||
|
fieldErrors={fieldErrors}
|
||||||
|
name="n"
|
||||||
|
label={LL.NAME(0)}
|
||||||
|
value={editItem.n}
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={8}>
|
||||||
|
<TextField name="t" label={LL.TYPE(0)} value={editItem.t} fullWidth select onChange={updateFormValue}>
|
||||||
|
{AnalogTypeNames.map((val, i) => (
|
||||||
|
<MenuItem key={i} value={i}>
|
||||||
|
{val}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
{editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE && (
|
||||||
|
<>
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<TextField name="u" label={LL.UNIT()} value={editItem.u} fullWidth select onChange={updateFormValue}>
|
||||||
|
{DeviceValueUOM_s.map((val, i) => (
|
||||||
|
<MenuItem key={i} value={i}>
|
||||||
|
{val}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
{editItem.t === AnalogType.ADC && (
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<TextField
|
||||||
|
name="o"
|
||||||
|
label={LL.OFFSET()}
|
||||||
|
value={numberValue(editItem.o)}
|
||||||
|
fullWidth
|
||||||
|
type="number"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
inputProps={{ min: '0', max: '3300', step: '1' }}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: <InputAdornment position="start">mV</InputAdornment>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
{editItem.t === AnalogType.COUNTER && (
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<TextField
|
||||||
|
name="o"
|
||||||
|
label={LL.STARTVALUE()}
|
||||||
|
value={numberValue(editItem.o)}
|
||||||
|
fullWidth
|
||||||
|
type="number"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
inputProps={{ step: '0.001' }}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<TextField
|
||||||
|
name="f"
|
||||||
|
label={LL.FACTOR()}
|
||||||
|
value={numberValue(editItem.f)}
|
||||||
|
fullWidth
|
||||||
|
type="number"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
inputProps={{ step: '0.001' }}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{editItem.t === AnalogType.DIGITAL_OUT && (editItem.g === 25 || editItem.g === 26) && (
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<TextField
|
||||||
|
name="o"
|
||||||
|
label={LL.VALUE(0)}
|
||||||
|
value={numberValue(editItem.o)}
|
||||||
|
fullWidth
|
||||||
|
type="number"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
inputProps={{ min: '0', max: '255', step: '1' }}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
{editItem.t === AnalogType.DIGITAL_OUT && editItem.g !== 25 && editItem.g !== 26 && (
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<TextField
|
||||||
|
name="o"
|
||||||
|
label={LL.VALUE(0)}
|
||||||
|
value={numberValue(editItem.o)}
|
||||||
|
fullWidth
|
||||||
|
type="number"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
inputProps={{ min: '0', max: '1', step: '1' }}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
{editItem.t >= AnalogType.PWM_0 && (
|
||||||
|
<>
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<TextField
|
||||||
|
name="f"
|
||||||
|
label={LL.FREQ()}
|
||||||
|
value={numberValue(editItem.f)}
|
||||||
|
fullWidth
|
||||||
|
type="number"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
inputProps={{ min: '1', max: '5000', step: '1' }}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: <InputAdornment position="start">Hz</InputAdornment>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<TextField
|
||||||
|
name="o"
|
||||||
|
label={LL.DUTY_CYCLE()}
|
||||||
|
value={numberValue(editItem.o)}
|
||||||
|
fullWidth
|
||||||
|
type="number"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
inputProps={{ min: '0', max: '100', step: '0.1' }}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: <InputAdornment position="start">%</InputAdornment>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
{!creating && (
|
||||||
|
<Box flexGrow={1} sx={{ '& button': { mt: 0 } }}>
|
||||||
|
<Button startIcon={<RemoveIcon />} variant="outlined" color="error" onClick={remove}>
|
||||||
|
{LL.REMOVE()}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Button startIcon={<CancelIcon />} variant="outlined" onClick={close} color="secondary">
|
||||||
|
{LL.CANCEL()}
|
||||||
|
</Button>
|
||||||
|
<Button startIcon={<WarningIcon color="warning" />} variant="contained" onClick={save} color="info">
|
||||||
|
{creating ? LL.ADD(0) : LL.UPDATE()}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardSensorsAnalogDialog;
|
||||||
121
interface/src/project/DashboardSensorsTemperatureDialog.tsx
Normal file
121
interface/src/project/DashboardSensorsTemperatureDialog.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
Box,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
InputAdornment,
|
||||||
|
Grid,
|
||||||
|
TextField
|
||||||
|
} from '@mui/material';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
import type { TemperatureSensor } from './types';
|
||||||
|
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';
|
||||||
|
|
||||||
|
type DashboardSensorsTemperatureDialogProps = {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSave: (ts: TemperatureSensor) => void;
|
||||||
|
selectedItem: TemperatureSensor;
|
||||||
|
validator: Schema;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DashboardSensorsTemperatureDialog = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSave,
|
||||||
|
selectedItem,
|
||||||
|
validator
|
||||||
|
}: DashboardSensorsTemperatureDialogProps) => {
|
||||||
|
const { LL } = useI18nContext();
|
||||||
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
const [editItem, setEditItem] = useState<TemperatureSensor>(selectedItem);
|
||||||
|
const updateFormValue = updateValue(setEditItem);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
setFieldErrors(undefined);
|
||||||
|
setEditItem(selectedItem);
|
||||||
|
}
|
||||||
|
}, [open, selectedItem]);
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
try {
|
||||||
|
setFieldErrors(undefined);
|
||||||
|
await validate(validator, editItem);
|
||||||
|
onSave(editItem);
|
||||||
|
} catch (errors: any) {
|
||||||
|
setFieldErrors(errors);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={close}>
|
||||||
|
<DialogTitle>
|
||||||
|
{LL.EDIT()} {LL.TEMP_SENSOR()}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
|
||||||
|
<Typography variant="body2">
|
||||||
|
{LL.ID_OF(LL.SENSOR())}: {editItem.id}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Grid container spacing={1}>
|
||||||
|
<Grid item>
|
||||||
|
<ValidatedTextField
|
||||||
|
fieldErrors={fieldErrors}
|
||||||
|
name="n"
|
||||||
|
label={LL.NAME(0)}
|
||||||
|
value={editItem.n}
|
||||||
|
autoFocus
|
||||||
|
sx={{ width: '30ch' }}
|
||||||
|
onChange={updateFormValue}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<TextField
|
||||||
|
name="o"
|
||||||
|
label={LL.OFFSET()}
|
||||||
|
value={numberValue(editItem.o)}
|
||||||
|
sx={{ width: '12ch' }}
|
||||||
|
type="number"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
inputProps={{ min: '-5', max: '5', step: '0.1' }}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: <InputAdornment position="start">°C</InputAdornment>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button startIcon={<CancelIcon />} variant="outlined" onClick={close} color="secondary">
|
||||||
|
{LL.CANCEL()}
|
||||||
|
</Button>
|
||||||
|
<Button startIcon={<WarningIcon color="warning" />} variant="contained" onClick={save} color="info">
|
||||||
|
{LL.UPDATE()}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardSensorsTemperatureDialog;
|
||||||
@@ -1,42 +1,38 @@
|
|||||||
import { FC, useState, useContext, useEffect } from 'react';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import { toast } from 'react-toastify';
|
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||||
|
import DirectionsBusIcon from '@mui/icons-material/DirectionsBus';
|
||||||
|
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Button,
|
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
ListItemAvatar,
|
|
||||||
ListItemText,
|
|
||||||
Box,
|
Box,
|
||||||
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Theme,
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemAvatar,
|
||||||
|
ListItemText,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
|
||||||
import { useTheme as tableTheme } from '@table-library/react-table-library/theme';
|
import { useTheme as tableTheme } from '@table-library/react-table-library/theme';
|
||||||
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
|
||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
|
||||||
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
|
||||||
import DirectionsBusIcon from '@mui/icons-material/DirectionsBus';
|
|
||||||
|
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
|
||||||
|
|
||||||
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
|
||||||
|
|
||||||
import { Status, busConnectionStatus, Stat } from './types';
|
|
||||||
|
|
||||||
import { extractErrorMessage, useRest } from 'utils';
|
|
||||||
|
|
||||||
import * as EMSESP from './api';
|
import * as EMSESP from './api';
|
||||||
|
import { busConnectionStatus } from './types';
|
||||||
|
import type { Stat, Status } from './types';
|
||||||
|
import type { Theme } from '@mui/material';
|
||||||
|
|
||||||
import type { Translation } from 'i18n/i18n-types';
|
import type { Translation } from 'i18n/i18n-types';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
||||||
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { extractErrorMessage, useRest } from 'utils';
|
||||||
|
|
||||||
export const isConnected = ({ status }: Status) => status !== busConnectionStatus.BUS_STATUS_OFFLINE;
|
export const isConnected = ({ status }: Status) => status !== busConnectionStatus.BUS_STATUS_OFFLINE;
|
||||||
|
|
||||||
@@ -77,24 +73,6 @@ const DashboardStatus: FC = () => {
|
|||||||
|
|
||||||
const { me } = useContext(AuthenticatedContext);
|
const { me } = useContext(AuthenticatedContext);
|
||||||
|
|
||||||
const showName = (id: any) => {
|
|
||||||
const name: keyof Translation['STATUS_NAMES'] = id;
|
|
||||||
return LL.STATUS_NAMES[name]();
|
|
||||||
};
|
|
||||||
|
|
||||||
const busStatus = ({ status }: Status) => {
|
|
||||||
switch (status) {
|
|
||||||
case busConnectionStatus.BUS_STATUS_CONNECTED:
|
|
||||||
return LL.CONNECTED(0);
|
|
||||||
case busConnectionStatus.BUS_STATUS_TX_ERRORS:
|
|
||||||
return LL.TX_ISSUES();
|
|
||||||
case busConnectionStatus.BUS_STATUS_OFFLINE:
|
|
||||||
return LL.DISCONNECTED();
|
|
||||||
default:
|
|
||||||
return 'Unknown';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const stats_theme = tableTheme({
|
const stats_theme = tableTheme({
|
||||||
Table: `
|
Table: `
|
||||||
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 90px 80px;
|
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 90px 80px;
|
||||||
@@ -138,18 +116,11 @@ const DashboardStatus: FC = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line
|
});
|
||||||
}, []);
|
|
||||||
|
|
||||||
const scan = async () => {
|
const showName = (id: any) => {
|
||||||
try {
|
const name: keyof Translation['STATUS_NAMES'] = id;
|
||||||
await EMSESP.scanDevices();
|
return LL.STATUS_NAMES[name]();
|
||||||
toast.info(LL.SCANNING() + '...');
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
|
||||||
} finally {
|
|
||||||
setConfirmScan(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDurationSec = (duration_sec: number) => {
|
const formatDurationSec = (duration_sec: number) => {
|
||||||
@@ -172,6 +143,31 @@ const DashboardStatus: FC = () => {
|
|||||||
return formatted;
|
return formatted;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const busStatus = () => {
|
||||||
|
if (data) {
|
||||||
|
switch (data.status) {
|
||||||
|
case busConnectionStatus.BUS_STATUS_CONNECTED:
|
||||||
|
return LL.CONNECTED(0) + ' (' + formatDurationSec(data.uptime) + ')';
|
||||||
|
case busConnectionStatus.BUS_STATUS_TX_ERRORS:
|
||||||
|
return LL.TX_ISSUES();
|
||||||
|
case busConnectionStatus.BUS_STATUS_OFFLINE:
|
||||||
|
return LL.DISCONNECTED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'Unknown';
|
||||||
|
};
|
||||||
|
|
||||||
|
const scan = async () => {
|
||||||
|
try {
|
||||||
|
await EMSESP.scanDevices();
|
||||||
|
toast.info(LL.SCANNING() + '...');
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
||||||
|
} finally {
|
||||||
|
setConfirmScan(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const renderScanDialog = () => (
|
const renderScanDialog = () => (
|
||||||
<Dialog open={confirmScan} onClose={() => setConfirmScan(false)}>
|
<Dialog open={confirmScan} onClose={() => setConfirmScan(false)}>
|
||||||
<DialogTitle>{LL.SCAN_DEVICES()}</DialogTitle>
|
<DialogTitle>{LL.SCAN_DEVICES()}</DialogTitle>
|
||||||
@@ -201,10 +197,7 @@ const DashboardStatus: FC = () => {
|
|||||||
<DirectionsBusIcon />
|
<DirectionsBusIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText
|
<ListItemText primary={LL.EMS_BUS_STATUS()} secondary={busStatus()} />
|
||||||
primary={LL.EMS_BUS_STATUS()}
|
|
||||||
secondary={busStatus(data) + ' (' + formatDurationSec(data.uptime) + ')'}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
|
|||||||
@@ -1,43 +1,21 @@
|
|||||||
import { FC } from 'react';
|
import { AiOutlineControl, AiOutlineGateway, AiOutlineAlert, AiOutlineChrome } 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 { MdThermostatAuto, MdOutlineSensors, MdOutlineExtension } from 'react-icons/md';
|
|
||||||
import { GiHeatHaze } from 'react-icons/gi';
|
import { GiHeatHaze } from 'react-icons/gi';
|
||||||
|
import { MdThermostatAuto, MdOutlineSensors, MdOutlineExtension } 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 { AiOutlineControl, AiOutlineGateway, AiOutlineAlert, AiOutlineChrome } from 'react-icons/ai';
|
import { DeviceType } from './types';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
interface DeviceIconProps {
|
interface DeviceIconProps {
|
||||||
type_id: number;
|
type_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// matches emsdevice.h DeviceType
|
|
||||||
const enum DeviceType {
|
|
||||||
SYSTEM = 0,
|
|
||||||
DALLASSENSOR,
|
|
||||||
ANALOGSENSOR,
|
|
||||||
SCHEDULER,
|
|
||||||
BOILER,
|
|
||||||
THERMOSTAT,
|
|
||||||
MIXER,
|
|
||||||
SOLAR,
|
|
||||||
HEATPUMP,
|
|
||||||
GATEWAY,
|
|
||||||
SWITCH,
|
|
||||||
CONTROLLER,
|
|
||||||
CONNECT,
|
|
||||||
ALERT,
|
|
||||||
PUMP,
|
|
||||||
GENERIC,
|
|
||||||
HEATSOURCE,
|
|
||||||
CUSTOM,
|
|
||||||
UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
|
const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
|
||||||
switch (type_id) {
|
switch (type_id) {
|
||||||
case DeviceType.DALLASSENSOR:
|
case DeviceType.TEMPERATURESENSOR:
|
||||||
case DeviceType.ANALOGSENSOR:
|
case DeviceType.ANALOGSENSOR:
|
||||||
return <MdOutlineSensors />;
|
return <MdOutlineSensors />;
|
||||||
case DeviceType.BOILER:
|
case DeviceType.BOILER:
|
||||||
|
|||||||
81
interface/src/project/EntityMaskToggle.tsx
Normal file
81
interface/src/project/EntityMaskToggle.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { ToggleButton, ToggleButtonGroup } from '@mui/material';
|
||||||
|
import OptionIcon from './OptionIcon';
|
||||||
|
import { DeviceEntityMask } from './types';
|
||||||
|
import type { DeviceEntity } from './types';
|
||||||
|
|
||||||
|
type EntityMaskToggleProps = {
|
||||||
|
onUpdate: (de: DeviceEntity) => void;
|
||||||
|
de: DeviceEntity;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EntityMaskToggle = ({ onUpdate, de }: EntityMaskToggleProps) => {
|
||||||
|
const getMaskNumber = (newMask: string[]) => {
|
||||||
|
let new_mask = 0;
|
||||||
|
for (const entry of newMask) {
|
||||||
|
new_mask |= Number(entry);
|
||||||
|
}
|
||||||
|
return new_mask;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMaskString = (m: number) => {
|
||||||
|
const new_masks: string[] = [];
|
||||||
|
if ((m & 1) === 1) {
|
||||||
|
new_masks.push('1');
|
||||||
|
}
|
||||||
|
if ((m & 2) === 2) {
|
||||||
|
new_masks.push('2');
|
||||||
|
}
|
||||||
|
if ((m & 4) === 4) {
|
||||||
|
new_masks.push('4');
|
||||||
|
}
|
||||||
|
if ((m & 8) === 8) {
|
||||||
|
new_masks.push('8');
|
||||||
|
}
|
||||||
|
if ((m & 128) === 128) {
|
||||||
|
new_masks.push('128');
|
||||||
|
}
|
||||||
|
return new_masks;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToggleButtonGroup
|
||||||
|
size="small"
|
||||||
|
color="secondary"
|
||||||
|
value={getMaskString(de.m)}
|
||||||
|
onChange={(event, mask) => {
|
||||||
|
de.m = getMaskNumber(mask);
|
||||||
|
if (de.n === '' && de.m & DeviceEntityMask.DV_READONLY) {
|
||||||
|
de.m = de.m | DeviceEntityMask.DV_WEB_EXCLUDE;
|
||||||
|
}
|
||||||
|
if (de.m & DeviceEntityMask.DV_WEB_EXCLUDE) {
|
||||||
|
de.m = de.m & ~DeviceEntityMask.DV_FAVORITE;
|
||||||
|
}
|
||||||
|
onUpdate(de);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ToggleButton value="8" disabled={(de.m & 0x81) !== 0 || de.n === undefined}>
|
||||||
|
<OptionIcon type="favorite" isSet={(de.m & DeviceEntityMask.DV_FAVORITE) === DeviceEntityMask.DV_FAVORITE} />
|
||||||
|
</ToggleButton>
|
||||||
|
<ToggleButton value="4" disabled={!de.w || (de.m & 0x83) >= 3}>
|
||||||
|
<OptionIcon type="readonly" isSet={(de.m & DeviceEntityMask.DV_READONLY) === DeviceEntityMask.DV_READONLY} />
|
||||||
|
</ToggleButton>
|
||||||
|
<ToggleButton value="2" disabled={de.n === '' || (de.m & 0x80) !== 0}>
|
||||||
|
<OptionIcon
|
||||||
|
type="api_mqtt_exclude"
|
||||||
|
isSet={(de.m & DeviceEntityMask.DV_API_MQTT_EXCLUDE) === DeviceEntityMask.DV_API_MQTT_EXCLUDE}
|
||||||
|
/>
|
||||||
|
</ToggleButton>
|
||||||
|
<ToggleButton value="1" disabled={de.n === undefined || (de.m & 0x80) !== 0}>
|
||||||
|
<OptionIcon
|
||||||
|
type="web_exclude"
|
||||||
|
isSet={(de.m & DeviceEntityMask.DV_WEB_EXCLUDE) === DeviceEntityMask.DV_WEB_EXCLUDE}
|
||||||
|
/>
|
||||||
|
</ToggleButton>
|
||||||
|
<ToggleButton value="128">
|
||||||
|
<OptionIcon type="deleted" isSet={(de.m & DeviceEntityMask.DV_DELETED) === DeviceEntityMask.DV_DELETED} />
|
||||||
|
</ToggleButton>
|
||||||
|
</ToggleButtonGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EntityMaskToggle;
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Tab } from '@mui/material';
|
import { Tab } from '@mui/material';
|
||||||
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
|
import HelpInformation from './HelpInformation';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
|
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
import HelpInformation from './HelpInformation';
|
|
||||||
|
|
||||||
const Help: FC = () => {
|
const Help: FC = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const { routerTab } = useRouterTab();
|
const { routerTab } = useRouterTab();
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
import { FC } from 'react';
|
import CommentIcon from '@mui/icons-material/CommentTwoTone';
|
||||||
|
import EastIcon from '@mui/icons-material/East';
|
||||||
|
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||||
|
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||||
|
import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone';
|
||||||
import { Typography, Button, Box, List, ListItem, ListItemText, Link, ListItemAvatar } from '@mui/material';
|
import { Typography, Button, Box, List, ListItem, ListItemText, Link, ListItemAvatar } from '@mui/material';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import * as EMSESP from './api';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { SectionContent } from 'components';
|
import { SectionContent } from 'components';
|
||||||
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import CommentIcon from '@mui/icons-material/CommentTwoTone';
|
|
||||||
import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone';
|
|
||||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
|
||||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
|
||||||
import EastIcon from '@mui/icons-material/East';
|
|
||||||
|
|
||||||
import { extractErrorMessage } from 'utils';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { extractErrorMessage } from 'utils';
|
||||||
import * as EMSESP from './api';
|
|
||||||
|
|
||||||
const HelpInformation: FC = () => {
|
const HelpInformation: FC = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
import { FC } from 'react';
|
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
||||||
import { SvgIconProps } from '@mui/material';
|
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||||
|
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
|
||||||
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
|
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
|
||||||
|
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||||
|
|
||||||
|
import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined';
|
||||||
import StarIcon from '@mui/icons-material/Star';
|
import 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 CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
import type { SvgIconProps } from '@mui/material';
|
||||||
import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
|
||||||
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
|
||||||
|
|
||||||
type OptionType = 'deleted' | 'readonly' | 'web_exclude' | 'api_mqtt_exclude' | 'favorite';
|
type OptionType = 'deleted' | 'readonly' | 'web_exclude' | 'api_mqtt_exclude' | 'favorite';
|
||||||
|
|
||||||
@@ -34,9 +32,9 @@ interface OptionIconProps {
|
|||||||
const OptionIcon: FC<OptionIconProps> = ({ type, isSet }) => {
|
const OptionIcon: FC<OptionIconProps> = ({ type, isSet }) => {
|
||||||
const Icon = OPTION_ICONS[type][isSet ? 0 : 1];
|
const Icon = OPTION_ICONS[type][isSet ? 0 : 1];
|
||||||
return isSet ? (
|
return isSet ? (
|
||||||
<Icon color="primary" sx={{ fontSize: 14, verticalAlign: 'middle' }} />
|
<Icon color="primary" sx={{ fontSize: 16, verticalAlign: 'middle' }} />
|
||||||
) : (
|
) : (
|
||||||
<Icon sx={{ fontSize: 14, verticalAlign: 'middle' }} />
|
<Icon sx={{ fontSize: 16, verticalAlign: 'middle' }} />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Tab } from '@mui/material';
|
import { Tab } from '@mui/material';
|
||||||
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
|
||||||
|
|
||||||
import SettingsApplication from './SettingsApplication';
|
import SettingsApplication from './SettingsApplication';
|
||||||
import SettingsCustomization from './SettingsCustomization';
|
import SettingsCustomization from './SettingsCustomization';
|
||||||
import SettingsScheduler from './SettingsScheduler';
|
|
||||||
import SettingsEntities from './SettingsEntities';
|
import SettingsEntities from './SettingsEntities';
|
||||||
|
import SettingsScheduler from './SettingsScheduler';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
|
||||||
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
const Settings: FC = () => {
|
const Settings: FC = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
@@ -24,7 +21,7 @@ const Settings: FC = () => {
|
|||||||
<Tab value="application" label={LL.APPLICATION_SETTINGS()} />
|
<Tab value="application" label={LL.APPLICATION_SETTINGS()} />
|
||||||
<Tab value="customization" label={LL.CUSTOMIZATIONS()} />
|
<Tab value="customization" label={LL.CUSTOMIZATIONS()} />
|
||||||
<Tab value="scheduler" label={LL.SCHEDULER()} />
|
<Tab value="scheduler" label={LL.SCHEDULER()} />
|
||||||
<Tab value="customentities" label={LL.CUSTOM_ENTITIES()} />
|
<Tab value="customentities" label={LL.CUSTOM_ENTITIES(0)} />
|
||||||
</RouterTabs>
|
</RouterTabs>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="application" element={<SettingsApplication />} />
|
<Route path="application" element={<SettingsApplication />} />
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user