Merge pull request #1931 from proddy/dev

Web changes #1920
This commit is contained in:
Proddy
2024-08-16 13:49:32 +02:00
committed by GitHub
103 changed files with 5342 additions and 5640 deletions

View File

@@ -21,6 +21,7 @@ export default tseslint.config(
{ {
rules: { rules: {
'@typescript-eslint/no-unsafe-enum-comparison': 'off', '@typescript-eslint/no-unsafe-enum-comparison': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off', '@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-misused-promises': [ '@typescript-eslint/no-misused-promises': [
'error', 'error',

View File

@@ -8,42 +8,33 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite dev",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"build-hosted": "typesafe-i18n --no-watch && vite build --mode hosted", "build-hosted": "typesafe-i18n --no-watch && vite build --mode hosted",
"preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"npm:mock-rest\" \"vite preview\"", "preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"npm:mock-rest\" \"vite preview\"",
"mock-rest": "bun --watch ../mock-api/rest_server.ts", "mock-rest": "bun --watch ../mock-api/rest_server.ts",
"mock-es": "bun --watch ../mock-api/es_server.ts", "standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"npm:mock-rest\" \"vite\"",
"mock-upload": "bun --watch ../mock-api/upload_server.ts",
"standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"npm:mock-rest\" \"npm:mock-es\" \"npm:mock-upload\" \"vite\"",
"typesafe-i18n": "typesafe-i18n --no-watch", "typesafe-i18n": "typesafe-i18n --no-watch",
"webUI": "node progmem-generator.js", "webUI": "node progmem-generator.js",
"format": "prettier -l -w '**/*.{ts,tsx,js,css,json,md}'", "format": "prettier -l -w '**/*.{ts,tsx,js,css,json,md}'",
"lint": "eslint . --fix" "lint": "eslint . --fix"
}, },
"dependencies": { "dependencies": {
"@alova/adapter-xhr": "^1.0.6", "@alova/adapter-xhr": "2.0.4",
"@alova/scene-react": "^1.6.2",
"@emotion/react": "^11.13.0", "@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0", "@emotion/styled": "^11.13.0",
"@mui/icons-material": "^5.16.6", "@mui/icons-material": "^5.16.7",
"@mui/material": "^5.16.6", "@mui/material": "^5.16.7",
"@table-library/react-table-library": "4.1.7", "@table-library/react-table-library": "4.1.7",
"@types/lodash-es": "^4.17.12", "alova": "3.0.9",
"@types/node": "^22.0.2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-router-dom": "^5.3.3",
"alova": "^2.21.4",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"history": "^5.3.0",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"lodash-es": "^4.17.21",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"react": "latest", "preact": "^10.23.2",
"react-dom": "latest", "react": "^18.3.1",
"react-dropzone": "^14.2.3", "react-dom": "^18.3.1",
"react-icons": "^5.2.1", "react-icons": "^5.3.0",
"react-router-dom": "^6.26.0", "react-router-dom": "^6.26.0",
"react-toastify": "^10.0.5", "react-toastify": "^10.0.5",
"typesafe-i18n": "^5.26.2", "typesafe-i18n": "^5.26.2",
@@ -51,22 +42,27 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",
"@eslint/js": "^9.8.0", "@eslint/js": "^9.9.0",
"@preact/compat": "^17.1.2", "@preact/compat": "^17.1.2",
"@preact/preset-vite": "^2.9.0", "@preact/preset-vite": "^2.9.0",
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/babel__core": "^7", "@types/babel__core": "^7",
"@types/formidable": "^3",
"@types/node": "^22.3.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-router-dom": "^5.3.3",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"eslint": "^9.8.0", "eslint": "^9.9.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"preact": "^10.23.1", "formidable": "^3.5.1",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"terser": "^5.31.3", "terser": "^5.31.6",
"typescript-eslint": "8.0.0", "typescript-eslint": "8.1.0",
"vite": "^5.3.5", "vite": "^5.4.1",
"vite-plugin-imagemin": "^0.6.1", "vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^4.3.2" "vite-tsconfig-paths": "^5.0.1"
}, },
"packageManager": "yarn@4.4.0" "packageManager": "yarn@4.4.0"
} }

View File

@@ -1,5 +1,6 @@
/* /*
* Uses font-size 400 (normal) only and Latin (plus extra unicode chars) to keep flash memory to a minimum * Uses font-weight 400 (normal) only, no bold, and Latin with a few extra unicode chars.
* This is to keep flash memory to a minimum
* View fonts on https://fonts.google.com/ * View fonts on https://fonts.google.com/
* Download woff2 using e.g. https://fonts.googleapis.com/css2?family=Lato or https://fonts.googleapis.com/css2?family=Roboto * Download woff2 using e.g. https://fonts.googleapis.com/css2?family=Lato or https://fonts.googleapis.com/css2?family=Roboto
*/ */

View File

@@ -1,5 +1,4 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import type { FC } from 'react';
import { Slide, ToastContainer } from 'react-toastify'; import { Slide, ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.min.css'; import 'react-toastify/dist/ReactToastify.min.css';
@@ -12,7 +11,7 @@ import { localStorageDetector } from 'typesafe-i18n/detectors';
const detectedLocale = detectLocale(localStorageDetector); const detectedLocale = detectLocale(localStorageDetector);
const App: FC = () => { const App = () => {
const [wasLoaded, setWasLoaded] = useState(false); const [wasLoaded, setWasLoaded] = useState(false);
useEffect(() => { useEffect(() => {

View File

@@ -1,6 +1,5 @@
import { useContext, useEffect } from 'react'; import { useContext, useEffect } from 'react';
import type { FC } from 'react'; import { Navigate, Route, Routes } from 'react-router-dom';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import AuthenticatedRouting from 'AuthenticatedRouting'; import AuthenticatedRouting from 'AuthenticatedRouting';
@@ -14,7 +13,7 @@ interface SecurityRedirectProps {
signOut?: boolean; signOut?: boolean;
} }
const RootRedirect: FC<SecurityRedirectProps> = ({ message, signOut }) => { const RootRedirect = ({ message, signOut }: SecurityRedirectProps) => {
const authenticationContext = useContext(AuthenticationContext); const authenticationContext = useContext(AuthenticationContext);
useEffect(() => { useEffect(() => {
signOut && authenticationContext.signOut(false); signOut && authenticationContext.signOut(false);
@@ -23,26 +22,11 @@ const RootRedirect: FC<SecurityRedirectProps> = ({ message, signOut }) => {
return <Navigate to="/" />; return <Navigate to="/" />;
}; };
export const RemoveTrailingSlashes = () => { const AppRouting = () => {
const location = useLocation();
return (
location.pathname.match('/.*/$') && (
<Navigate
to={{
pathname: location.pathname.replace(/\/+$/, ''),
search: location.search
}}
/>
)
);
};
const AppRouting: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
return ( return (
<Authentication> <Authentication>
<RemoveTrailingSlashes />
<Routes> <Routes>
<Route <Route
path="/unauthorized" path="/unauthorized"

View File

@@ -1,18 +1,19 @@
import { type FC, useContext } from 'react'; import { useContext } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom'; import { Navigate, Route, Routes } from 'react-router-dom';
import CustomEntities from 'app/main/CustomEntities'; import CustomEntities from 'app/main/CustomEntities';
import Customization from 'app/main/Customization'; import Customizations from 'app/main/Customizations';
import Devices from 'app/main/Devices'; import Devices from 'app/main/Devices';
import Help from 'app/main/Help';
import Modules from 'app/main/Modules'; import Modules from 'app/main/Modules';
import Scheduler from 'app/main/Scheduler'; import Scheduler from 'app/main/Scheduler';
import Sensors from 'app/main/Sensors'; import Sensors from 'app/main/Sensors';
import APSettings from 'app/settings/APSettings'; import APSettings from 'app/settings/APSettings';
import ApplicationSettings from 'app/settings/ApplicationSettings'; import ApplicationSettings from 'app/settings/ApplicationSettings';
import DownloadUpload from 'app/settings/DownloadUpload';
import MqttSettings from 'app/settings/MqttSettings'; import MqttSettings from 'app/settings/MqttSettings';
import NTPSettings from 'app/settings/NTPSettings'; import NTPSettings from 'app/settings/NTPSettings';
import Settings from 'app/settings/Settings'; import Settings from 'app/settings/Settings';
import UploadDownload from 'app/settings/UploadDownload';
import Network from 'app/settings/network/Network'; import Network from 'app/settings/network/Network';
import Security from 'app/settings/security/Security'; import Security from 'app/settings/security/Security';
import APStatus from 'app/status/APStatus'; import APStatus from 'app/status/APStatus';
@@ -26,9 +27,7 @@ import SystemLog from 'app/status/SystemLog';
import { Layout } from 'components'; import { Layout } from 'components';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import Help from './app/main/Help'; const AuthenticatedRouting = () => {
const AuthenticatedRouting: FC = () => {
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
return ( return (
<Layout> <Layout>
@@ -55,12 +54,12 @@ const AuthenticatedRouting: FC = () => {
<Route path="/settings/ntp" element={<NTPSettings />} /> <Route path="/settings/ntp" element={<NTPSettings />} />
<Route path="/settings/ap" element={<APSettings />} /> <Route path="/settings/ap" element={<APSettings />} />
<Route path="/settings/modules" element={<Modules />} /> <Route path="/settings/modules" element={<Modules />} />
<Route path="/settings/upload" element={<UploadDownload />} /> <Route path="/settings/upload" element={<DownloadUpload />} />
<Route path="/settings/network/*" element={<Network />} /> <Route path="/settings/network/*" element={<Network />} />
<Route path="/settings/security/*" element={<Security />} /> <Route path="/settings/security/*" element={<Security />} />
<Route path="/customizations" element={<Customization />} /> <Route path="/customizations" element={<Customizations />} />
<Route path="/scheduler" element={<Scheduler />} /> <Route path="/scheduler" element={<Scheduler />} />
<Route path="/customentities" element={<CustomEntities />} /> <Route path="/customentities" element={<CustomEntities />} />
</> </>

View File

@@ -1,14 +1,11 @@
import { useContext, useState } from 'react'; import { useContext, useState } from 'react';
import type { FC } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import ForwardIcon from '@mui/icons-material/Forward'; import ForwardIcon from '@mui/icons-material/Forward';
import { Box, Button, Paper, Typography } from '@mui/material'; import { Box, Button, Paper, Typography } from '@mui/material';
import * as AuthenticationApi from 'api/authentication'; import * as AuthenticationApi from 'components/routing/authentication';
import { PROJECT_NAME } from 'api/env'; import { useRequest } from 'alova/client';
import { useRequest } from 'alova';
import type { ValidateFieldsError } from 'async-validator'; import type { ValidateFieldsError } from 'async-validator';
import { import {
LanguageSelector, LanguageSelector,
@@ -16,12 +13,13 @@ import {
ValidatedTextField ValidatedTextField
} from 'components'; } from 'components';
import { AuthenticationContext } from 'contexts/authentication'; import { AuthenticationContext } from 'contexts/authentication';
import { PROJECT_NAME } from 'env';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import type { SignInRequest } from 'types'; import type { SignInRequest } from 'types';
import { onEnterCallback, updateValue } from 'utils'; import { onEnterCallback, updateValue } from 'utils';
import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators'; import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators';
const SignIn: FC = () => { const SignIn = () => {
const authenticationContext = useContext(AuthenticationContext); const authenticationContext = useContext(AuthenticationContext);
const { LL } = useI18nContext(); const { LL } = useI18nContext();
@@ -33,14 +31,12 @@ const SignIn: FC = () => {
const [processing, setProcessing] = useState<boolean>(false); const [processing, setProcessing] = useState<boolean>(false);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const { send: callSignIn, onSuccess } = useRequest( const { send: callSignIn } = useRequest(
(request: SignInRequest) => AuthenticationApi.signIn(request), (request: SignInRequest) => AuthenticationApi.signIn(request),
{ {
immediate: false immediate: false
} }
); ).onSuccess((response) => {
onSuccess((response) => {
if (response.data) { if (response.data) {
authenticationContext.signIn(response.data.access_token); authenticationContext.signIn(response.data.access_token);
} }
@@ -113,6 +109,10 @@ const SignIn: FC = () => {
onChange={updateLoginRequestValue} onChange={updateLoginRequestValue}
margin="normal" margin="normal"
variant="outlined" variant="outlined"
inputProps={{
autoCapitalize: 'none',
autoCorrect: 'off'
}}
/> />
<ValidatedPasswordField <ValidatedPasswordField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}

View File

@@ -17,7 +17,7 @@ import type {
Settings, Settings,
WriteAnalogSensor, WriteAnalogSensor,
WriteTemperatureSensor WriteTemperatureSensor
} from './types'; } from '../app/main/types';
// DashboardDevices // DashboardDevices
export const readCoreData = () => alovaInstance.Get<CoreData>(`/rest/coreData`); export const readCoreData = () => alovaInstance.Get<CoreData>(`/rest/coreData`);
@@ -65,7 +65,7 @@ export const readDeviceEntities = (id: number) =>
alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, { alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, {
params: { id }, params: { id },
responseType: 'arraybuffer', responseType: 'arraybuffer',
transformData(data) { transform(data) {
return (data as DeviceEntity[]).map((de: DeviceEntity) => ({ return (data as DeviceEntity[]).map((de: DeviceEntity) => ({
...de, ...de,
o_m: de.m, o_m: de.m,
@@ -88,8 +88,7 @@ export const writeDeviceName = (data: { id: number; name: string }) =>
// SettingsScheduler // SettingsScheduler
export const readSchedule = () => export const readSchedule = () =>
alovaInstance.Get<ScheduleItem[]>('/rest/schedule', { alovaInstance.Get<ScheduleItem[]>('/rest/schedule', {
name: 'schedule', transform(data) {
transformData(data) {
return (data as Schedule).schedule.map((si: ScheduleItem) => ({ return (data as Schedule).schedule.map((si: ScheduleItem) => ({
...si, ...si,
o_id: si.id, o_id: si.id,
@@ -109,8 +108,7 @@ export const writeSchedule = (data: Schedule) =>
// Modules // Modules
export const readModules = () => export const readModules = () =>
alovaInstance.Get<ModuleItem[]>('/rest/modules', { alovaInstance.Get<ModuleItem[]>('/rest/modules', {
name: 'modules', transform(data) {
transformData(data) {
return (data as Modules).modules.map((mi: ModuleItem) => ({ return (data as Modules).modules.map((mi: ModuleItem) => ({
...mi, ...mi,
o_enabled: mi.enabled, o_enabled: mi.enabled,
@@ -127,8 +125,7 @@ export const writeModules = (data: {
// SettingsEntities // SettingsEntities
export const readCustomEntities = () => export const readCustomEntities = () =>
alovaInstance.Get<EntityItem[]>('/rest/customEntities', { alovaInstance.Get<EntityItem[]>('/rest/customEntities', {
name: 'entities', transform(data) {
transformData(data) {
return (data as Entities).entities.map((ei: EntityItem) => ({ return (data as Entities).entities.map((ei: EntityItem) => ({
...ei, ...ei,
o_id: ei.id, o_id: ei.id,

View File

@@ -1,19 +1,19 @@
import { xhrRequestAdapter } from '@alova/adapter-xhr'; import { type AlovaXHRResponse, xhrRequestAdapter } from '@alova/adapter-xhr';
import { createAlova } from 'alova'; import { createAlova } from 'alova';
import ReactHook from 'alova/react'; import ReactHook from 'alova/react';
import { unpack } from '../api/unpack'; import { unpack } from './unpack';
export const ACCESS_TOKEN = 'access_token'; export const ACCESS_TOKEN = 'access_token';
export const alovaInstance = createAlova({ export const alovaInstance = createAlova({
statesHook: ReactHook, statesHook: ReactHook,
timeout: 3000, // 3 seconds but throwing a timeout error timeout: 3000, // 3 seconds before throwing a timeout error
localCache: null, cacheFor: null, // disable cache
// localCache: { // cacheFor: {
// GET: { // GET: {
// mode: 'placeholder', // see https://alova.js.org/learning/response-cache/#cache-replaceholder-mode // mode: 'memory',
// expire: 2000 // expire: 60 * 10 * 1000 // 60 seconds in cache
// } // }
// }, // },
requestAdapter: xhrRequestAdapter(), requestAdapter: xhrRequestAdapter(),
@@ -22,10 +22,15 @@ export const alovaInstance = createAlova({
method.config.headers.Authorization = method.config.headers.Authorization =
'Bearer ' + localStorage.getItem(ACCESS_TOKEN); 'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
} }
// for simualting vrey slow networks
// return new Promise((resolve) => {
// const random = 3000 + Math.random() * 2000;
// setTimeout(resolve, Math.floor(random));
// });
}, },
responded: { responded: {
onSuccess: async (response) => { onSuccess: async (response: AlovaXHRResponse) => {
// if (response.status === 202) { // if (response.status === 202) {
// throw new Error('Wait'); // wifi scan in progress // throw new Error('Wait'); // wifi scan in progress
// } else // } else

View File

@@ -7,12 +7,9 @@ export const readNetworkStatus = () =>
export const scanNetworks = () => alovaInstance.Get('/rest/scanNetworks'); export const scanNetworks = () => alovaInstance.Get('/rest/scanNetworks');
export const listNetworks = () => export const listNetworks = () =>
alovaInstance.Get<WiFiNetworkList>('/rest/listNetworks', { alovaInstance.Get<WiFiNetworkList>('/rest/listNetworks', {
name: 'listNetworks',
timeout: 20000 // timeout 20 seconds timeout: 20000 // timeout 20 seconds
}); });
export const readNetworkSettings = () => export const readNetworkSettings = () =>
alovaInstance.Get<NetworkSettingsType>('/rest/networkSettings', { alovaInstance.Get<NetworkSettingsType>('/rest/networkSettings');
name: 'networkSettings'
});
export const updateNetworkSettings = (wifiSettings: NetworkSettingsType) => export const updateNetworkSettings = (wifiSettings: NetworkSettingsType) =>
alovaInstance.Post<NetworkSettingsType>('/rest/networkSettings', wifiSettings); alovaInstance.Post<NetworkSettingsType>('/rest/networkSettings', wifiSettings);

View File

@@ -4,10 +4,9 @@ import { alovaInstance } from './endpoints';
export const readNTPStatus = () => export const readNTPStatus = () =>
alovaInstance.Get<NTPStatusType>('/rest/ntpStatus'); alovaInstance.Get<NTPStatusType>('/rest/ntpStatus');
export const readNTPSettings = () => export const readNTPSettings = () =>
alovaInstance.Get<NTPSettingsType>('/rest/ntpSettings', { alovaInstance.Get<NTPSettingsType>('/rest/ntpSettings', {});
name: 'ntpSettings'
});
export const updateNTPSettings = (data: NTPSettingsType) => export const updateNTPSettings = (data: NTPSettingsType) =>
alovaInstance.Post<NTPSettingsType>('/rest/ntpSettings', data); alovaInstance.Post<NTPSettingsType>('/rest/ntpSettings', data);

View File

@@ -1,8 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import type { HardwareStatus, LogSettings, SystemStatus } from 'types'; import type { HardwareStatus, LogSettings, SystemStatus } from 'types';
import { alovaInstance, alovaInstanceGH } from './endpoints'; import { alovaInstance, alovaInstanceGH } from './endpoints';
@@ -31,13 +26,13 @@ export const fetchLogES = () => alovaInstance.Get('/es/log');
// Get versions from github // Get versions from github
export const getStableVersion = () => export const getStableVersion = () =>
alovaInstanceGH.Get('latest', { alovaInstanceGH.Get('latest', {
transformData(response) { transform(response: { data: { name: string } }) {
return response.data.name.substring(1); return response.data.name.substring(1);
} }
}); });
export const getDevVersion = () => export const getDevVersion = () =>
alovaInstanceGH.Get('tags/latest', { alovaInstanceGH.Get('tags/latest', {
transformData(response) { transform(response: { data: { name: string } }) {
return response.data.name.split(/\s+/).splice(-1)[0].substring(1); return response.data.name.split(/\s+/).splice(-1)[0].substring(1);
} }
}); });

View File

@@ -1,12 +1,10 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import type { FC } from 'react';
import { useBlocker } from 'react-router-dom'; import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import RefreshIcon from '@mui/icons-material/Refresh';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { Box, Button, Typography } from '@mui/material'; import { Box, Button, Typography } from '@mui/material';
@@ -20,7 +18,7 @@ import {
Table Table
} from '@table-library/react-table-library/table'; } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme'; import { useTheme } from '@table-library/react-table-library/theme';
import { updateState, useRequest } from 'alova'; import { updateState, useAutoRequest, useRequest } from 'alova/client';
import { import {
BlockNavigation, BlockNavigation,
ButtonRow, ButtonRow,
@@ -30,13 +28,13 @@ import {
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api'; import { readCustomEntities, writeCustomEntities } from '../../api/app';
import SettingsCustomEntitiesDialog from './CustomEntitiesDialog'; import SettingsCustomEntitiesDialog from './CustomEntitiesDialog';
import { DeviceValueTypeNames, DeviceValueUOM_s } from './types'; import { DeviceValueTypeNames, DeviceValueUOM_s } from './types';
import type { Entities, EntityItem } from './types'; import type { Entities, EntityItem } from './types';
import { entityItemValidation } from './validators'; import { entityItemValidation } from './validators';
const CustomEntities: FC = () => { const CustomEntities = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0); const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0); const blocker = useBlocker(numChanges !== 0);
@@ -50,13 +48,13 @@ const CustomEntities: FC = () => {
data: entities, data: entities,
send: fetchEntities, send: fetchEntities,
error error
} = useRequest(EMSESP.readCustomEntities, { } = useAutoRequest(readCustomEntities, {
initialData: [], initialData: [],
force: true pollingTime: 2000
}); });
const { send: writeEntities } = useRequest( const { send: writeEntities } = useRequest(
(data: Entities) => EMSESP.writeCustomEntities(data), (data: Entities) => writeCustomEntities(data),
{ immediate: false } { immediate: false }
); );
@@ -182,8 +180,7 @@ const CustomEntities: FC = () => {
const onDialogSave = (updatedItem: EntityItem) => { const onDialogSave = (updatedItem: EntityItem) => {
setDialogOpen(false); setDialogOpen(false);
void updateState(readCustomEntities(), (data: EntityItem[]) => {
updateState('entities', (data: EntityItem[]) => {
const new_data = creating const new_data = creating
? [ ? [
...data.filter((ei) => creating || ei.o_id !== updatedItem.o_id), ...data.filter((ei) => creating || ei.o_id !== updatedItem.o_id),
@@ -327,15 +324,6 @@ const CustomEntities: FC = () => {
)} )}
</Box> </Box>
<Box flexWrap="nowrap" whiteSpace="nowrap"> <Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow>
<Button
startIcon={<RefreshIcon />}
variant="outlined"
color="secondary"
onClick={fetchEntities}
>
{LL.REFRESH()}
</Button>
<Button <Button
startIcon={<AddIcon />} startIcon={<AddIcon />}
variant="outlined" variant="outlined"
@@ -344,7 +332,6 @@ const CustomEntities: FC = () => {
> >
{LL.ADD(0)} {LL.ADD(0)}
</Button> </Button>
</ButtonRow>
</Box> </Box>
</Box> </Box>
</SectionContent> </SectionContent>

View File

@@ -291,7 +291,11 @@ const CustomEntitiesDialog = ({
fullWidth fullWidth
margin="normal" margin="normal"
type="number" type="number"
inputProps={{ min: '1', max: String(256 - editItem.offset), step: '1' }} inputProps={{
min: '1',
max: String(256 - editItem.offset),
step: '1'
}}
/> />
</Grid> </Grid>
)} )}

View File

@@ -1,5 +1,4 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import type { FC } from 'react';
import { useBlocker, useLocation } from 'react-router-dom'; import { useBlocker, useLocation } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
@@ -27,7 +26,7 @@ import {
Typography Typography
} from '@mui/material'; } from '@mui/material';
import * as SystemApi from 'api/system'; import { restart } from 'api/system';
import { import {
Body, Body,
@@ -40,7 +39,7 @@ import {
} from '@table-library/react-table-library/table'; } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme'; import { useTheme } from '@table-library/react-table-library/theme';
import { dialogStyle } from 'CustomTheme'; import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova'; import { useRequest } from 'alova/client';
import RestartMonitor from 'app/status/RestartMonitor'; import RestartMonitor from 'app/status/RestartMonitor';
import { import {
BlockNavigation, BlockNavigation,
@@ -51,8 +50,14 @@ import {
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api'; import {
import SettingsCustomizationDialog from './CustomizationDialog'; readDeviceEntities,
readDevices,
resetCustomizations,
writeCustomizationEntities,
writeDeviceName
} from '../../api/app';
import SettingsCustomizationsDialog from './CustomizationsDialog';
import EntityMaskToggle from './EntityMaskToggle'; import EntityMaskToggle from './EntityMaskToggle';
import OptionIcon from './OptionIcon'; import OptionIcon from './OptionIcon';
import { DeviceEntityMask } from './types'; import { DeviceEntityMask } from './types';
@@ -60,7 +65,7 @@ import type { DeviceEntity, DeviceShort } from './types';
export const APIURL = window.location.origin + '/api/'; export const APIURL = window.location.origin + '/api/';
const Customization: FC = () => { const Customizations = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0); const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0); const blocker = useBlocker(numChanges !== 0);
@@ -78,7 +83,7 @@ const Customization: FC = () => {
useLayoutTitle(LL.CUSTOMIZATIONS()); useLayoutTitle(LL.CUSTOMIZATIONS());
// fetch devices first // fetch devices first
const { data: devices, send: fetchDevices } = useRequest(EMSESP.readDevices); const { data: devices, send: fetchDevices } = useRequest(readDevices);
const [selectedDevice, setSelectedDevice] = useState<number>( const [selectedDevice, setSelectedDevice] = useState<number>(
Number(useLocation().state) || -1 Number(useLocation().state) || -1
@@ -87,32 +92,33 @@ const Customization: FC = () => {
useState<string>(''); // needed for API URL useState<string>(''); // needed for API URL
const [selectedDeviceName, setSelectedDeviceName] = useState<string>(''); const [selectedDeviceName, setSelectedDeviceName] = useState<string>('');
const { send: resetCustomizations } = useRequest(EMSESP.resetCustomizations(), { const { send: sendResetCustomizations } = useRequest(resetCustomizations(), {
immediate: false immediate: false
}); });
const { send: writeDeviceName } = useRequest( const { send: sendDeviceName } = useRequest(
(data: { id: number; name: string }) => EMSESP.writeDeviceName(data), (data: { id: number; name: string }) => writeDeviceName(data),
{ {
immediate: false immediate: false
} }
); );
const { send: writeCustomizationEntities } = useRequest( const { send: sendCustomizationEntities } = useRequest(
(data: { id: number; entity_ids: string[] }) => (data: { id: number; entity_ids: string[] }) => writeCustomizationEntities(data),
EMSESP.writeCustomizationEntities(data),
{ {
immediate: false immediate: false
} }
); );
const { send: readDeviceEntities, onSuccess: onSuccess } = useRequest( const { send: sendDeviceEntities } = useRequest(
(data: number) => EMSESP.readDeviceEntities(data), (data: number) => readDeviceEntities(data),
{ {
initialData: [], initialData: [],
immediate: false immediate: false
} }
); ).onSuccess((event) => {
setOriginalSettings(event.data);
});
const setOriginalSettings = (data: DeviceEntity[]) => { const setOriginalSettings = (data: DeviceEntity[]) => {
setDeviceEntities( setDeviceEntities(
@@ -126,11 +132,7 @@ const Customization: FC = () => {
); );
}; };
onSuccess((event) => { const { send: sendRestart } = useRequest(restart(), {
setOriginalSettings(event.data);
});
const { send: restartCommand } = useRequest(SystemApi.restart(), {
immediate: false immediate: false
}); });
@@ -231,7 +233,7 @@ const Customization: FC = () => {
useEffect(() => { useEffect(() => {
if (devices && selectedDevice !== -1) { if (devices && selectedDevice !== -1) {
void readDeviceEntities(selectedDevice); void sendDeviceEntities(selectedDevice);
const id = devices.devices.findIndex((d) => d.i === selectedDevice); const id = devices.devices.findIndex((d) => d.i === selectedDevice);
if (id === -1) { if (id === -1) {
setSelectedDevice(-1); setSelectedDevice(-1);
@@ -245,8 +247,8 @@ const Customization: FC = () => {
} }
}, [devices, selectedDevice]); }, [devices, selectedDevice]);
const restart = async () => { const doRestart = async () => {
await restartCommand().catch((error: Error) => { await sendRestart().catch((error: Error) => {
toast.error(error.message); toast.error(error.message);
}); });
setRestarting(true); setRestarting(true);
@@ -327,7 +329,7 @@ const Customization: FC = () => {
const resetCustomization = async () => { const resetCustomization = async () => {
try { try {
await resetCustomizations(); await sendResetCustomizations();
toast.info(LL.CUSTOMIZATIONS_RESTART()); toast.info(LL.CUSTOMIZATIONS_RESTART());
} catch (error) { } catch (error) {
toast.error((error as Error).message); toast.error((error as Error).message);
@@ -387,7 +389,7 @@ const Customization: FC = () => {
return; return;
} }
await writeCustomizationEntities({ await sendCustomizationEntities({
id: selectedDevice, id: selectedDevice,
entity_ids: masked_entities entity_ids: masked_entities
}).catch((error: Error) => { }).catch((error: Error) => {
@@ -402,7 +404,7 @@ const Customization: FC = () => {
}; };
const renameDevice = async () => { const renameDevice = async () => {
await writeDeviceName({ id: selectedDevice, name: selectedDeviceName }) await sendDeviceName({ id: selectedDevice, name: selectedDeviceName })
.then(() => { .then(() => {
toast.success(LL.UPDATED_OF(LL.NAME(1))); toast.success(LL.UPDATED_OF(LL.NAME(1)));
}) })
@@ -676,7 +678,7 @@ const Customization: FC = () => {
startIcon={<PowerSettingsNewIcon />} startIcon={<PowerSettingsNewIcon />}
variant="contained" variant="contained"
color="error" color="error"
onClick={restart} onClick={doRestart}
> >
{LL.RESTART()} {LL.RESTART()}
</Button> </Button>
@@ -691,7 +693,7 @@ const Customization: FC = () => {
startIcon={<CancelIcon />} startIcon={<CancelIcon />}
variant="outlined" variant="outlined"
color="secondary" color="secondary"
onClick={() => devices && readDeviceEntities(selectedDevice)} onClick={() => devices && sendDeviceEntities(selectedDevice)}
> >
{LL.CANCEL()} {LL.CANCEL()}
</Button> </Button>
@@ -727,7 +729,7 @@ const Customization: FC = () => {
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <RestartMonitor /> : renderContent()} {restarting ? <RestartMonitor /> : renderContent()}
{selectedDeviceEntity && ( {selectedDeviceEntity && (
<SettingsCustomizationDialog <SettingsCustomizationsDialog
open={dialogOpen} open={dialogOpen}
onClose={onDialogClose} onClose={onDialogClose}
onSave={onDialogSave} onSave={onDialogSave}
@@ -738,4 +740,4 @@ const Customization: FC = () => {
); );
}; };
export default Customization; export default Customizations;

View File

@@ -23,19 +23,19 @@ import EntityMaskToggle from './EntityMaskToggle';
import { DeviceEntityMask } from './types'; import { DeviceEntityMask } from './types';
import type { DeviceEntity } from './types'; import type { DeviceEntity } from './types';
interface SettingsCustomizationDialogProps { interface SettingsCustomizationsDialogProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
onSave: (di: DeviceEntity) => void; onSave: (di: DeviceEntity) => void;
selectedItem: DeviceEntity; selectedItem: DeviceEntity;
} }
const CustomizationDialog = ({ const CustomizationsDialog = ({
open, open,
onClose, onClose,
onSave, onSave,
selectedItem selectedItem
}: SettingsCustomizationDialogProps) => { }: SettingsCustomizationsDialogProps) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<DeviceEntity>(selectedItem); const [editItem, setEditItem] = useState<DeviceEntity>(selectedItem);
const [error, setError] = useState<boolean>(false); const [error, setError] = useState<boolean>(false);
@@ -175,4 +175,4 @@ const CustomizationDialog = ({
); );
}; };
export default CustomizationDialog; export default CustomizationsDialog;

View File

@@ -1,4 +1,3 @@
import type { FC } from 'react';
import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/ai'; import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/ai';
import { CgSmartHomeBoiler } from 'react-icons/cg'; import { CgSmartHomeBoiler } from 'react-icons/cg';
import { FaSolarPanel } from 'react-icons/fa'; import { FaSolarPanel } from 'react-icons/fa';
@@ -16,12 +15,8 @@ import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
import { DeviceType } from './types'; import { DeviceType } from './types';
interface DeviceIconProps { const DeviceIcon = ({ type_id }: { type_id: DeviceType }) => {
type_id: number; switch (type_id) {
}
const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
switch (type_id as DeviceType) {
case DeviceType.TEMPERATURESENSOR: case DeviceType.TEMPERATURESENSOR:
case DeviceType.ANALOGSENSOR: case DeviceType.ANALOGSENSOR:
return <MdOutlineSensors />; return <MdOutlineSensors />;

View File

@@ -5,7 +5,6 @@ import {
useLayoutEffect, useLayoutEffect,
useState useState
} from 'react'; } from 'react';
import type { FC } from 'react';
import { IconContext } from 'react-icons'; import { IconContext } from 'react-icons';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
@@ -20,13 +19,13 @@ import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined'; import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined'; import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
import PlayArrowIcon from '@mui/icons-material/PlayArrow'; import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import RefreshIcon from '@mui/icons-material/Refresh';
import StarIcon from '@mui/icons-material/Star'; import StarIcon from '@mui/icons-material/Star';
import StarBorderOutlinedIcon from '@mui/icons-material/StarBorderOutlined'; import StarBorderOutlinedIcon from '@mui/icons-material/StarBorderOutlined';
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined'; import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
import { import {
Box, Box,
Button, Button,
CircularProgress,
Dialog, Dialog,
DialogActions, DialogActions,
DialogContent, DialogContent,
@@ -57,12 +56,12 @@ import {
import { useTheme } from '@table-library/react-table-library/theme'; import { useTheme } from '@table-library/react-table-library/theme';
import type { Action, State } from '@table-library/react-table-library/types/common'; import type { Action, State } from '@table-library/react-table-library/types/common';
import { dialogStyle } from 'CustomTheme'; import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova'; import { useRequest } from 'alova/client';
import { ButtonRow, MessageBox, SectionContent, useLayoutTitle } from 'components'; import { MessageBox, SectionContent, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api'; import { readCoreData, readDeviceData, writeDeviceValue } from '../../api/app';
import DeviceIcon from './DeviceIcon'; import DeviceIcon from './DeviceIcon';
import DashboardDevicesDialog from './DevicesDialog'; import DashboardDevicesDialog from './DevicesDialog';
import { formatValue } from './deviceValue'; import { formatValue } from './deviceValue';
@@ -70,7 +69,7 @@ import { DeviceEntityMask, DeviceType, DeviceValueUOM_s } from './types';
import type { Device, DeviceValue } from './types'; import type { Device, DeviceValue } from './types';
import { deviceValueItemValidation } from './validators'; import { deviceValueItemValidation } from './validators';
const Devices: FC = () => { const Devices = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
@@ -85,18 +84,15 @@ const Devices: FC = () => {
useLayoutTitle(LL.DEVICES()); useLayoutTitle(LL.DEVICES());
const { data: coreData, send: readCoreData } = useRequest( const { data: coreData, send: sendCoreData } = useRequest(() => readCoreData(), {
() => EMSESP.readCoreData(),
{
initialData: { initialData: {
connected: true, connected: true,
devices: [] devices: []
} }
} });
);
const { data: deviceData, send: readDeviceData } = useRequest( const { data: deviceData, send: sendDeviceData } = useRequest(
(id: number) => EMSESP.readDeviceData(id), (id: number) => readDeviceData(id),
{ {
initialData: { initialData: {
data: [] data: []
@@ -105,8 +101,8 @@ const Devices: FC = () => {
} }
); );
const { loading: submitting, send: writeDeviceValue } = useRequest( const { loading: submitting, send: sendDeviceValue } = useRequest(
(data: { id: number; c: string; v: unknown }) => EMSESP.writeDeviceValue(data), (data: { id: number; c: string; v: unknown }) => writeDeviceValue(data),
{ {
immediate: false immediate: false
} }
@@ -288,7 +284,7 @@ const Devices: FC = () => {
async function onSelectChange(action: Action, state: State) { async function onSelectChange(action: Action, state: State) {
setSelectedDevice(state.id as number); setSelectedDevice(state.id as number);
if (action.type === 'ADD_BY_ID_EXCLUSIVELY') { if (action.type === 'ADD_BY_ID_EXCLUSIVELY') {
await readDeviceData(state.id as number); await sendDeviceData(state.id as number);
} }
} }
@@ -321,12 +317,6 @@ const Devices: FC = () => {
}; };
}, [escFunction]); }, [escFunction]);
const refreshData = () => {
if (!deviceValueDialogOpen) {
selectedDevice ? void readDeviceData(selectedDevice) : void readCoreData();
}
};
const customize = () => { const customize = () => {
if (selectedDevice == 99) { if (selectedDevice == 99) {
navigate('/customentities'); navigate('/customentities');
@@ -437,7 +427,12 @@ const Devices: FC = () => {
}; };
useEffect(() => { useEffect(() => {
const timer = setInterval(() => refreshData(), 60000); const timer = setInterval(() => {
if (deviceValueDialogOpen) {
return;
}
selectedDevice ? void sendDeviceData(selectedDevice) : void sendCoreData();
}, 2000);
return () => { return () => {
clearInterval(timer); clearInterval(timer);
}; };
@@ -445,7 +440,7 @@ const Devices: FC = () => {
const deviceValueDialogSave = async (devicevalue: DeviceValue) => { const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
const id = Number(device_select.state.id); const id = Number(device_select.state.id);
await writeDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v }) await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v })
.then(() => { .then(() => {
toast.success(LL.WRITE_CMD_SENT()); toast.success(LL.WRITE_CMD_SENT());
}) })
@@ -454,7 +449,7 @@ const Devices: FC = () => {
}) })
.finally(async () => { .finally(async () => {
setDeviceValueDialogOpen(false); setDeviceValueDialogOpen(false);
await readDeviceData(id); await sendDeviceData(id);
setSelectedDeviceValue(undefined); setSelectedDeviceValue(undefined);
}); });
}; };
@@ -568,6 +563,9 @@ const Devices: FC = () => {
</HeaderRow> </HeaderRow>
</Header> </Header>
<Body> <Body>
{tableList.length === 0 && (
<CircularProgress sx={{ margin: 1 }} size={24} />
)}
{tableList.map((device: Device) => ( {tableList.map((device: Device) => (
<Row key={device.id} item={device}> <Row key={device.id} item={device}>
<Cell stiff> <Cell stiff>
@@ -592,6 +590,7 @@ const Devices: FC = () => {
const deviceValueDialogClose = () => { const deviceValueDialogClose = () => {
setDeviceValueDialogOpen(false); setDeviceValueDialogOpen(false);
void sendDeviceData(selectedDevice);
}; };
const renderDeviceData = () => { const renderDeviceData = () => {
@@ -685,11 +684,6 @@ const Devices: FC = () => {
)} )}
</IconButton> </IconButton>
</ButtonTooltip> </ButtonTooltip>
<ButtonTooltip title={LL.REFRESH()}>
<IconButton onClick={refreshData}>
<RefreshIcon color="primary" sx={{ fontSize: 18 }} />
</IconButton>
</ButtonTooltip>
</Typography> </Typography>
<Grid item zeroMinWidth justifyContent="flex-end"> <Grid item zeroMinWidth justifyContent="flex-end">
<ButtonTooltip title={LL.CANCEL()}> <ButtonTooltip title={LL.CANCEL()}>
@@ -784,16 +778,6 @@ const Devices: FC = () => {
progress={submitting} progress={submitting}
/> />
)} )}
<ButtonRow mt={1}>
<Button
startIcon={<RefreshIcon />}
variant="outlined"
color="secondary"
onClick={refreshData}
>
{LL.REFRESH()}
</Button>
</ButtonRow>
</SectionContent> </SectionContent>
); );
}; };

View File

@@ -1,4 +1,3 @@
import type { FC } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import CommentIcon from '@mui/icons-material/CommentTwoTone'; import CommentIcon from '@mui/icons-material/CommentTwoTone';
@@ -18,25 +17,20 @@ import {
Typography Typography
} from '@mui/material'; } from '@mui/material';
import * as EMSESP from 'app/main/api'; import { useRequest } from 'alova/client';
import { useRequest } from 'alova';
import { SectionContent, useLayoutTitle } from 'components'; import { SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { API } from '../../api/app';
import type { APIcall } from './types'; import type { APIcall } from './types';
const Help: FC = () => { const Help = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle(LL.HELP_OF('')); useLayoutTitle(LL.HELP_OF(''));
const { send: getAPI, onSuccess: onGetAPI } = useRequest( const { send: getAPI } = useRequest((data: APIcall) => API(data), {
(data: APIcall) => EMSESP.API(data),
{
immediate: false immediate: false
} }).onSuccess((event) => {
);
onGetAPI((event) => {
const anchor = document.createElement('a'); const anchor = document.createElement('a');
anchor.href = URL.createObjectURL( anchor.href = URL.createObjectURL(
new Blob([JSON.stringify(event.data, null, 2)], { new Blob([JSON.stringify(event.data, null, 2)], {
@@ -45,7 +39,7 @@ const Help: FC = () => {
); );
anchor.download = anchor.download =
'emsesp_' + event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt'; 'emsesp_' + event.args[0].device + '_' + event.args[0].entity + '.txt';
anchor.click(); anchor.click();
URL.revokeObjectURL(anchor.href); URL.revokeObjectURL(anchor.href);
toast.info(LL.DOWNLOAD_SUCCESSFUL()); toast.info(LL.DOWNLOAD_SUCCESSFUL());
@@ -58,6 +52,7 @@ const Help: FC = () => {
}; };
return ( return (
<>
<SectionContent> <SectionContent>
<List sx={{ borderRadius: 3, border: '2px solid grey' }}> <List sx={{ borderRadius: 3, border: '2px solid grey' }}>
<ListItem> <ListItem>
@@ -139,6 +134,7 @@ const Help: FC = () => {
</Typography> </Typography>
</Box> </Box>
</SectionContent> </SectionContent>
</>
); );
}; };

View File

@@ -1,5 +1,4 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import type { FC } from 'react';
import { useBlocker } from 'react-router-dom'; import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
@@ -18,7 +17,7 @@ import {
Table Table
} from '@table-library/react-table-library/table'; } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme'; import { useTheme } from '@table-library/react-table-library/theme';
import { updateState, useRequest } from 'alova'; import { updateState, useRequest } from 'alova/client';
import { import {
BlockNavigation, BlockNavigation,
ButtonRow, ButtonRow,
@@ -28,11 +27,11 @@ import {
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api'; import { readModules, writeModules } from '../../api/app';
import ModulesDialog from './ModulesDialog'; import ModulesDialog from './ModulesDialog';
import type { ModuleItem, Modules } from './types'; import type { ModuleItem, Modules } from './types';
const Modules: FC = () => { const Modules = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0); const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0); const blocker = useBlocker(numChanges !== 0);
@@ -44,13 +43,12 @@ const Modules: FC = () => {
data: modules, data: modules,
send: fetchModules, send: fetchModules,
error error
} = useRequest(EMSESP.readModules, { } = useRequest(readModules, {
initialData: [] initialData: []
}); });
const { send: writeModules } = useRequest( const { send: updateModules } = useRequest(
(data: { key: string; enabled: boolean; license: string }) => (data: { key: string; enabled: boolean; license: string }) => writeModules(data),
EMSESP.writeModules(data),
{ {
immediate: false immediate: false
} }
@@ -123,7 +121,7 @@ const Modules: FC = () => {
} }
const updateModuleItem = (updatedItem: ModuleItem) => { const updateModuleItem = (updatedItem: ModuleItem) => {
updateState('modules', (data: ModuleItem[]) => { void updateState(readModules(), (data: ModuleItem[]) => {
const new_data = data.map((mi) => const new_data = data.map((mi) =>
mi.id === updatedItem.id ? { ...mi, ...updatedItem } : mi mi.id === updatedItem.id ? { ...mi, ...updatedItem } : mi
); );
@@ -133,7 +131,7 @@ const Modules: FC = () => {
}; };
const saveModules = async () => { const saveModules = async () => {
await writeModules({ await updateModules({
modules: modules.map((condensed_mi) => ({ modules: modules.map((condensed_mi) => ({
key: condensed_mi.key, key: condensed_mi.key,
enabled: condensed_mi.enabled, enabled: condensed_mi.enabled,

View File

@@ -1,5 +1,3 @@
import type { FC } from 'react';
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined'; import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
@@ -32,12 +30,7 @@ const OPTION_ICONS: {
favorite: [StarIcon, StarOutlineIcon] favorite: [StarIcon, StarOutlineIcon]
}; };
interface OptionIconProps { const OptionIcon = ({ type, isSet }: { type: OptionType; isSet: boolean }) => {
type: OptionType;
isSet: boolean;
}
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: 16, verticalAlign: 'middle' }} /> <Icon color="primary" sx={{ fontSize: 16, verticalAlign: 'middle' }} />

View File

@@ -1,5 +1,4 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import type { FC } from 'react';
import { useBlocker } from 'react-router-dom'; import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
@@ -19,7 +18,7 @@ import {
Table Table
} from '@table-library/react-table-library/table'; } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme'; import { useTheme } from '@table-library/react-table-library/theme';
import { updateState, useRequest } from 'alova'; import { updateState, useRequest } from 'alova/client';
import { import {
BlockNavigation, BlockNavigation,
ButtonRow, ButtonRow,
@@ -29,13 +28,13 @@ import {
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api'; import { readSchedule, writeSchedule } from '../../api/app';
import SettingsSchedulerDialog from './SchedulerDialog'; import SettingsSchedulerDialog from './SchedulerDialog';
import { ScheduleFlag } from './types'; import { ScheduleFlag } from './types';
import type { Schedule, ScheduleItem } from './types'; import type { Schedule, ScheduleItem } from './types';
import { schedulerItemValidation } from './validators'; import { schedulerItemValidation } from './validators';
const Scheduler: FC = () => { const Scheduler = () => {
const { LL, locale } = useI18nContext(); const { LL, locale } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0); const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0); const blocker = useBlocker(numChanges !== 0);
@@ -48,13 +47,12 @@ const Scheduler: FC = () => {
data: schedule, data: schedule,
send: fetchSchedule, send: fetchSchedule,
error error
} = useRequest(EMSESP.readSchedule, { } = useRequest(readSchedule, {
initialData: [], initialData: []
force: true
}); });
const { send: writeSchedule } = useRequest( const { send: updateSchedule } = useRequest(
(data: Schedule) => EMSESP.writeSchedule(data), (data: Schedule) => writeSchedule(data),
{ {
immediate: false immediate: false
} }
@@ -131,7 +129,7 @@ const Scheduler: FC = () => {
}); });
const saveSchedule = async () => { const saveSchedule = async () => {
await writeSchedule({ await updateSchedule({
schedule: schedule schedule: schedule
.filter((si) => !si.deleted) .filter((si) => !si.deleted)
.map((condensed_si) => ({ .map((condensed_si) => ({
@@ -177,8 +175,7 @@ const Scheduler: FC = () => {
const onDialogSave = (updatedItem: ScheduleItem) => { const onDialogSave = (updatedItem: ScheduleItem) => {
setDialogOpen(false); setDialogOpen(false);
void updateState(readSchedule(), (data: ScheduleItem[]) => {
updateState('schedule', (data: ScheduleItem[]) => {
const new_data = creating const new_data = creating
? [ ? [
...data.filter((si) => creating || si.o_id !== updatedItem.o_id), ...data.filter((si) => creating || si.o_id !== updatedItem.o_id),

View File

@@ -208,7 +208,7 @@ const SchedulerDialog = ({
scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE ? 'primary' : 'grey' scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE ? 'primary' : 'grey'
} }
> >
{LL.ONCHANGE(0)} {LL.ONCHANGE()}
</Typography> </Typography>
</ToggleButton> </ToggleButton>
<ToggleButton value={ScheduleFlag.SCHEDULE_CONDITION}> <ToggleButton value={ScheduleFlag.SCHEDULE_CONDITION}>
@@ -218,7 +218,7 @@ const SchedulerDialog = ({
scheduleType === ScheduleFlag.SCHEDULE_CONDITION ? 'primary' : 'grey' scheduleType === ScheduleFlag.SCHEDULE_CONDITION ? 'primary' : 'grey'
} }
> >
{LL.CONDITION(0)} {LL.CONDITION()}
</Typography> </Typography>
</ToggleButton> </ToggleButton>
<ToggleButton value={ScheduleFlag.SCHEDULE_IMMEDIATE}> <ToggleButton value={ScheduleFlag.SCHEDULE_IMMEDIATE}>
@@ -228,7 +228,7 @@ const SchedulerDialog = ({
scheduleType === ScheduleFlag.SCHEDULE_IMMEDIATE ? 'primary' : 'grey' scheduleType === ScheduleFlag.SCHEDULE_IMMEDIATE ? 'primary' : 'grey'
} }
> >
{LL.IMMEDIATE(0)} {LL.IMMEDIATE()}
</Typography> </Typography>
</ToggleButton> </ToggleButton>
</ToggleButtonGroup> </ToggleButtonGroup>
@@ -311,10 +311,10 @@ const SchedulerDialog = ({
name="time" name="time"
label={ label={
scheduleType === ScheduleFlag.SCHEDULE_CONDITION scheduleType === ScheduleFlag.SCHEDULE_CONDITION
? LL.CONDITION(1) ? LL.CONDITION()
: scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE : scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE
? LL.ONCHANGE(1) ? LL.ONCHANGE()
: LL.IMMEDIATE(1) : LL.IMMEDIATE()
} }
multiline multiline
fullWidth fullWidth

View File

@@ -1,11 +1,9 @@
import { useContext, useEffect, useState } from 'react'; import { useContext, useState } from 'react';
import type { FC } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined'; import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined'; import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined'; import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
import RefreshIcon from '@mui/icons-material/Refresh';
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined'; import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
import { Box, Button, Typography } from '@mui/material'; import { Box, Button, Typography } from '@mui/material';
@@ -21,12 +19,16 @@ import {
} from '@table-library/react-table-library/table'; } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme'; import { useTheme } from '@table-library/react-table-library/theme';
import type { State } from '@table-library/react-table-library/types/common'; import type { State } from '@table-library/react-table-library/types/common';
import { useRequest } from 'alova'; import { useAutoRequest, useRequest } from 'alova/client';
import { ButtonRow, SectionContent, useLayoutTitle } from 'components'; import { SectionContent, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api'; import {
readSensorData,
writeAnalogSensor,
writeTemperatureSensor
} from '../../api/app';
import DashboardSensorsAnalogDialog from './SensorsAnalogDialog'; import DashboardSensorsAnalogDialog from './SensorsAnalogDialog';
import DashboardSensorsTemperatureDialog from './SensorsTemperatureDialog'; import DashboardSensorsTemperatureDialog from './SensorsTemperatureDialog';
import { import {
@@ -46,7 +48,7 @@ import {
temperatureSensorItemValidation temperatureSensorItemValidation
} from './validators'; } from './validators';
const Sensors: FC = () => { const Sensors = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
@@ -57,27 +59,25 @@ const Sensors: FC = () => {
const [analogDialogOpen, setAnalogDialogOpen] = useState<boolean>(false); const [analogDialogOpen, setAnalogDialogOpen] = useState<boolean>(false);
const [creating, setCreating] = useState<boolean>(false); const [creating, setCreating] = useState<boolean>(false);
const { data: sensorData, send: fetchSensorData } = useRequest( const { data: sensorData } = useAutoRequest(() => readSensorData(), {
() => EMSESP.readSensorData(),
{
initialData: { initialData: {
ts: [], ts: [],
as: [], as: [],
analog_enabled: false, analog_enabled: false,
platform: 'ESP32' platform: 'ESP32'
} },
} pollingTime: 2000
); });
const { send: writeTemperatureSensor } = useRequest( const { send: sendTemperatureSensor } = useRequest(
(data: WriteTemperatureSensor) => EMSESP.writeTemperatureSensor(data), (data: WriteTemperatureSensor) => writeTemperatureSensor(data),
{ {
immediate: false immediate: false
} }
); );
const { send: writeAnalogSensor } = useRequest( const { send: sendAnalogSensor } = useRequest(
(data: WriteAnalogSensor) => EMSESP.writeAnalogSensor(data), (data: WriteAnalogSensor) => writeAnalogSensor(data),
{ {
immediate: false immediate: false
} }
@@ -195,13 +195,6 @@ const Sensors: FC = () => {
} }
); );
useEffect(() => {
const timer = setInterval(() => fetchSensorData(), 30000);
return () => {
clearInterval(timer);
};
});
useLayoutTitle(LL.SENSORS()); useLayoutTitle(LL.SENSORS());
const formatDurationMin = (duration_min: number) => { const formatDurationMin = (duration_min: number) => {
@@ -266,17 +259,16 @@ const Sensors: FC = () => {
}; };
const onTemperatureDialogSave = async (ts: TemperatureSensor) => { const onTemperatureDialogSave = async (ts: TemperatureSensor) => {
await writeTemperatureSensor({ id: ts.id, name: ts.n, offset: ts.o }) await sendTemperatureSensor({ id: ts.id, name: ts.n, offset: ts.o })
.then(() => { .then(() => {
toast.success(LL.UPDATED_OF(LL.SENSOR(1))); toast.success(LL.UPDATED_OF(LL.SENSOR(1)));
}) })
.catch(() => { .catch(() => {
toast.error(LL.UPDATE_OF(LL.SENSOR(2)) + ' ' + LL.FAILED(1)); toast.error(LL.UPDATE_OF(LL.SENSOR(2)) + ' ' + LL.FAILED(1));
}) })
.finally(async () => { .finally(() => {
setTemperatureDialogOpen(false); setTemperatureDialogOpen(false);
setSelectedTemperatureSensor(undefined); setSelectedTemperatureSensor(undefined);
await fetchSensorData();
}); });
}; };
@@ -311,7 +303,7 @@ const Sensors: FC = () => {
}; };
const onAnalogDialogSave = async (as: AnalogSensor) => { const onAnalogDialogSave = async (as: AnalogSensor) => {
await writeAnalogSensor({ await sendAnalogSensor({
id: as.id, id: as.id,
gpio: as.g, gpio: as.g,
name: as.n, name: as.n,
@@ -327,10 +319,9 @@ const Sensors: FC = () => {
.catch(() => { .catch(() => {
toast.error(LL.UPDATE_OF(LL.ANALOG_SENSOR(5)) + ' ' + LL.FAILED(1)); toast.error(LL.UPDATE_OF(LL.ANALOG_SENSOR(5)) + ' ' + LL.FAILED(1));
}) })
.finally(async () => { .finally(() => {
setAnalogDialogOpen(false); setAnalogDialogOpen(false);
setSelectedAnalogSensor(undefined); setSelectedAnalogSensor(undefined);
await fetchSensorData();
}); });
}; };
@@ -474,8 +465,6 @@ const Sensors: FC = () => {
)} )}
/> />
)} )}
{sensorData?.analog_enabled === true && (
<>
<Typography sx={{ pt: 4, pb: 1 }} variant="h6" color="secondary"> <Typography sx={{ pt: 4, pb: 1 }} variant="h6" color="secondary">
{LL.ANALOG_SENSORS()} {LL.ANALOG_SENSORS()}
</Typography> </Typography>
@@ -495,32 +484,18 @@ const Sensors: FC = () => {
)} )}
/> />
)} )}
</>
)}
<ButtonRow>
<Box mt={1} display="flex" flexWrap="wrap">
<Box flexGrow={1}>
<Button
startIcon={<RefreshIcon />}
variant="outlined"
color="secondary"
onClick={fetchSensorData}
>
{LL.REFRESH()}
</Button>
</Box>
{sensorData?.analog_enabled === true && me.admin && ( {sensorData?.analog_enabled === true && me.admin && (
<Box mt={1} display="flex" flexWrap="wrap" justifyContent="flex-end">
<Button <Button
variant="outlined" variant="outlined"
color="primary" color="primary"
startIcon={<AddCircleOutlineOutlinedIcon />} startIcon={<AddCircleOutlineOutlinedIcon />}
onClick={addAnalogSensor} onClick={addAnalogSensor}
> >
{LL.ADD(0) + ' ' + LL.ANALOG_SENSOR(1)} {LL.ADD(0)}
</Button> </Button>
)}
</Box> </Box>
</ButtonRow> )}
</SectionContent> </SectionContent>
); );
}; };

View File

@@ -1,5 +1,4 @@
import { useState } from 'react'; import { useState } from 'react';
import type { FC } from 'react';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
@@ -19,7 +18,6 @@ import {
useLayoutTitle useLayoutTitle
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { range } from 'lodash-es';
import type { APSettingsType } from 'types'; import type { APSettingsType } from 'types';
import { APProvisionMode } from 'types'; import { APProvisionMode } from 'types';
import { numberValue, updateValueDirty, useRest } from 'utils'; import { numberValue, updateValueDirty, useRest } from 'utils';
@@ -29,7 +27,7 @@ export const isAPEnabled = ({ provision_mode }: APSettingsType) =>
provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_ALWAYS ||
provision_mode === APProvisionMode.AP_MODE_DISCONNECTED; provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
const APSettings: FC = () => { const APSettings = () => {
const { const {
loadData, loadData,
saving, saving,
@@ -74,6 +72,11 @@ const APSettings: FC = () => {
} }
}; };
// no lodash - https://asleepace.com/blog/typescript-range-without-a-loop/
function range(a: number, b: number): number[] {
return a < b ? [a, ...range(a + 1, b)] : [b];
}
return ( return (
<> <>
<ValidatedTextField <ValidatedTextField

View File

@@ -1,5 +1,4 @@
import { useState } from 'react'; import { useState } from 'react';
import type { FC } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
@@ -17,9 +16,9 @@ import {
Typography Typography
} from '@mui/material'; } from '@mui/material';
import * as SystemApi from 'api/system'; import { readHardwareStatus, restart } from 'api/system';
import { useRequest } from 'alova'; import { useRequest } from 'alova/client';
import RestartMonitor from 'app/status/RestartMonitor'; import RestartMonitor from 'app/status/RestartMonitor';
import type { ValidateFieldsError } from 'async-validator'; import type { ValidateFieldsError } from 'async-validator';
import { import {
@@ -36,7 +35,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValueDirty, useRest } from 'utils'; import { numberValue, updateValueDirty, useRest } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
import * as EMSESP from '../main/api'; import { getBoardProfile, readSettings, writeSettings } from '../../api/app';
import { BOARD_PROFILES } from '../main/types'; import { BOARD_PROFILES } from '../main/types';
import type { Settings } from '../main/types'; import type { Settings } from '../main/types';
import { createSettingsValidator } from '../main/validators'; import { createSettingsValidator } from '../main/validators';
@@ -49,15 +48,12 @@ export function boardProfileSelectItems() {
)); ));
} }
const ApplicationSettings: FC = () => { const ApplicationSettings = () => {
const { data: hardwareData } = useRequest(SystemApi.readHardwareStatus, { const { data: hardwareData } = useRequest(readHardwareStatus);
force: true
});
const { const {
loadData, loadData,
saveData, saveData,
saving,
updateDataValue, updateDataValue,
data, data,
origData, origData,
@@ -67,8 +63,8 @@ const ApplicationSettings: FC = () => {
errorMessage, errorMessage,
restartNeeded restartNeeded
} = useRest<Settings>({ } = useRest<Settings>({
read: EMSESP.readSettings, read: readSettings,
update: EMSESP.writeSettings update: writeSettings
}); });
const [restarting, setRestarting] = useState<boolean>(); const [restarting, setRestarting] = useState<boolean>();
@@ -84,19 +80,12 @@ const ApplicationSettings: FC = () => {
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const { const { loading: processingBoard, send: readBoardProfile } = useRequest(
loading: processingBoard, (boardProfile: string) => getBoardProfile(boardProfile),
send: readBoardProfile, {
onSuccess: onSuccessBoardProfile
} = useRequest((boardProfile: string) => EMSESP.getBoardProfile(boardProfile), {
immediate: false immediate: false
}); }
).onSuccess((event) => {
const { send: restartCommand } = useRequest(SystemApi.restart(), {
immediate: false
});
onSuccessBoardProfile((event) => {
const response = event.data as Settings; const response = event.data as Settings;
updateDataValue({ updateDataValue({
...data, ...data,
@@ -113,6 +102,10 @@ const ApplicationSettings: FC = () => {
}); });
}); });
const { send: restartCommand } = useRequest(restart(), {
immediate: false
});
const updateBoardProfile = async (board_profile: string) => { const updateBoardProfile = async (board_profile: string) => {
await readBoardProfile(board_profile).catch((error: Error) => { await readBoardProfile(board_profile).catch((error: Error) => {
toast.error(error.message); toast.error(error.message);
@@ -122,7 +115,7 @@ const ApplicationSettings: FC = () => {
useLayoutTitle(LL.SETTINGS_OF(LL.APPLICATION())); useLayoutTitle(LL.SETTINGS_OF(LL.APPLICATION()));
const content = () => { const content = () => {
if (!data) { if (!data || !hardwareData) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />; return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
} }
@@ -161,16 +154,321 @@ const ApplicationSettings: FC = () => {
return ( return (
<> <>
<Typography sx={{ pb: 1 }} variant="h6" color="primary"> <Typography sx={{ pb: 1 }} variant="h6" color="primary">
{LL.INTERFACE_BOARD_PROFILE()} {LL.SERVICES()}
</Typography>
<Typography color="secondary">API</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.notoken_api}
onChange={updateFormValue}
name="notoken_api"
/>
}
label={LL.BYPASS_TOKEN()}
/>
<Typography color="secondary">Console</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.telnet_enabled}
onChange={updateFormValue}
name="telnet_enabled"
/>
}
label={LL.ENABLE_TELNET()}
/>
<Typography color="secondary">Modbus</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.modbus_enabled}
onChange={updateFormValue}
name="modbus_enabled"
disabled={!hardwareData.psram}
/>
}
label={LL.ENABLE_MODBUS()}
/>
{data.modbus_enabled && (
<Grid
container
spacing={1}
direction="row"
justifyContent="flex-start"
alignItems="flex-start"
>
<Grid item xs={12} sm={6}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="modbus_max_clients"
label={LL.AP_MAX_CLIENTS()}
fullWidth
variant="outlined"
value={numberValue(data.modbus_max_clients)}
type="number"
onChange={updateFormValue}
margin="normal"
/>
</Grid>
<Grid item xs={12} sm={6}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="modbus_port"
label="Port"
fullWidth
variant="outlined"
value={numberValue(data.modbus_port)}
type="number"
onChange={updateFormValue}
margin="normal"
/>
</Grid>
<Grid item xs={12} sm={6}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="modbus_timeout"
label="Timeout"
InputProps={{
endAdornment: <InputAdornment position="end">ms</InputAdornment>
}}
fullWidth
variant="outlined"
value={numberValue(data.modbus_timeout)}
type="number"
onChange={updateFormValue}
margin="normal"
/>
</Grid>
</Grid>
)}
<Typography color="secondary">Syslog</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.syslog_enabled}
onChange={updateFormValue}
name="syslog_enabled"
/>
}
label={LL.ENABLE_SYSLOG()}
/>
{data.syslog_enabled && (
<Grid
container
spacing={1}
direction="row"
justifyContent="flex-start"
alignItems="flex-start"
>
<Grid item xs={12} sm={6}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="syslog_host"
label="Host"
fullWidth
variant="outlined"
value={data.syslog_host}
onChange={updateFormValue}
margin="normal"
/>
</Grid>
<Grid item xs={12} sm={6}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="syslog_port"
label="Port"
fullWidth
variant="outlined"
value={numberValue(data.syslog_port)}
type="number"
onChange={updateFormValue}
margin="normal"
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
name="syslog_level"
label={LL.LOG_LEVEL()}
value={data.syslog_level}
fullWidth
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={-1}>OFF</MenuItem>
<MenuItem value={3}>ERR</MenuItem>
<MenuItem value={5}>NOTICE</MenuItem>
<MenuItem value={6}>INFO</MenuItem>
<MenuItem value={7}>DEBUG</MenuItem>
<MenuItem value={9}>ALL</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={6}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="syslog_mark_interval"
label={LL.MARK_INTERVAL()}
InputProps={{
endAdornment: (
<InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
)
}}
fullWidth
variant="outlined"
value={numberValue(data.syslog_mark_interval)}
type="number"
onChange={updateFormValue}
margin="normal"
/>
</Grid>
</Grid>
)}
<Typography sx={{ pb: 1, pt: 2 }} variant="h6" color="primary">
{LL.SENSORS()}
</Typography>
<Typography color="secondary">Analog</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.analog_enabled}
onChange={updateFormValue}
name="analog_enabled"
/>
}
label={LL.ENABLE_ANALOG()}
/>
{data.dallas_gpio !== 0 && (
<>
<Typography color="secondary">{LL.TEMPERATURE()}</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.dallas_parasite}
onChange={updateFormValue}
name="dallas_parasite"
/>
}
label={LL.ENABLE_PARASITE()}
/>
</>
)}
<Typography sx={{ pb: 1, pt: 2 }} variant="h6" color="primary">
{LL.FORMATTING_OPTIONS()}
</Typography>
<Grid item>
<TextField
name="locale"
label={LL.LANGUAGE_ENTITIES()}
value={data.locale}
fullWidth
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value="de">Deutsch (DE)</MenuItem>
<MenuItem value="en">English (EN)</MenuItem>
<MenuItem value="fr">Français (FR)</MenuItem>
<MenuItem value="it">Italiano (IT)</MenuItem>
<MenuItem value="nl">Nederlands (NL)</MenuItem>
<MenuItem value="no">Norsk (NO)</MenuItem>
<MenuItem value="pl">Polski (PL)</MenuItem>
<MenuItem value="sk">Slovenčina (SK)</MenuItem>
<MenuItem value="sv">Svenska (SV)</MenuItem>
<MenuItem value="tr">Türk (TR)</MenuItem>
</TextField>
</Grid>
<Grid
container
spacing={1}
direction="row"
justifyContent="flex-start"
alignItems="flex-start"
>
<Grid item xs={12} sm={6} md={4}>
<TextField
name="bool_dashboard"
label={LL.BOOLEAN_FORMAT_DASHBOARD()}
value={data.bool_dashboard}
fullWidth
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={1}>{LL.ONOFF()}</MenuItem>
<MenuItem value={2}>{LL.ONOFF_CAP()}</MenuItem>
<MenuItem value={3}>true/false</MenuItem>
<MenuItem value={5}>1/0</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<TextField
name="bool_format"
label={LL.BOOLEAN_FORMAT_API()}
value={data.bool_format}
fullWidth
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={1}>{LL.ONOFF()}</MenuItem>
<MenuItem value={2}>{LL.ONOFF_CAP()}</MenuItem>
<MenuItem value={3}>&quot;true&quot;/&quot;false&quot;</MenuItem>
<MenuItem value={4}>true/false</MenuItem>
<MenuItem value={5}>&quot;1&quot;/&quot;0&quot;</MenuItem>
<MenuItem value={6}>1/0</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<TextField
name="enum_format"
label={LL.ENUM_FORMAT()}
value={data.enum_format}
fullWidth
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={1}>{LL.VALUE(5)}</MenuItem>
<MenuItem value={2}>{LL.INDEX()}</MenuItem>
</TextField>
</Grid>
</Grid>
<BlockFormControlLabel
control={
<Checkbox
checked={data.fahrenheit}
onChange={updateFormValue}
name="fahrenheit"
/>
}
label={LL.CONVERT_FAHRENHEIT()}
/>
<BlockFormControlLabel
control={
<Checkbox
checked={data.trace_raw}
onChange={updateFormValue}
name="trace_raw"
/>
}
label={LL.LOG_HEX()}
/>
<Typography sx={{ pb: 1, pt: 2 }} variant="h6" color="primary">
{LL.SETTINGS_OF(LL.HARDWARE())}
</Typography> </Typography>
<Box color="warning.main">
<Typography variant="body2">{LL.BOARD_PROFILE_TEXT()}</Typography>
</Box>
<TextField <TextField
name="board_profile" name="board_profile"
label={LL.BOARD_PROFILE()} label={LL.BOARD_PROFILE()}
value={data.board_profile} value={data.board_profile}
disabled={processingBoard} disabled={processingBoard || hardwareData.model.startsWith('BBQKees')}
fullWidth fullWidth
variant="outlined" variant="outlined"
onChange={changeBoardProfile} onChange={changeBoardProfile}
@@ -204,7 +502,6 @@ const ApplicationSettings: FC = () => {
type="number" type="number"
onChange={updateFormValue} onChange={updateFormValue}
margin="normal" margin="normal"
disabled={saving}
/> />
</Grid> </Grid>
<Grid item xs={12} sm={6} md={4}> <Grid item xs={12} sm={6} md={4}>
@@ -218,7 +515,6 @@ const ApplicationSettings: FC = () => {
type="number" type="number"
onChange={updateFormValue} onChange={updateFormValue}
margin="normal" margin="normal"
disabled={saving}
/> />
</Grid> </Grid>
<Grid item xs={12} sm={6} md={4}> <Grid item xs={12} sm={6} md={4}>
@@ -232,7 +528,6 @@ const ApplicationSettings: FC = () => {
type="number" type="number"
onChange={updateFormValue} onChange={updateFormValue}
margin="normal" margin="normal"
disabled={saving}
/> />
</Grid> </Grid>
<Grid item xs={12} sm={6} md={4}> <Grid item xs={12} sm={6} md={4}>
@@ -248,7 +543,6 @@ const ApplicationSettings: FC = () => {
type="number" type="number"
onChange={updateFormValue} onChange={updateFormValue}
margin="normal" margin="normal"
disabled={saving}
/> />
</Grid> </Grid>
<Grid item xs={12} sm={6} md={4}> <Grid item xs={12} sm={6} md={4}>
@@ -262,14 +556,12 @@ const ApplicationSettings: FC = () => {
type="number" type="number"
onChange={updateFormValue} onChange={updateFormValue}
margin="normal" margin="normal"
disabled={saving}
/> />
</Grid> </Grid>
<Grid item xs={12} sm={6} md={4}> <Grid item xs={12} sm={6} md={4}>
<TextField <TextField
name="phy_type" name="phy_type"
label={LL.PHY_TYPE()} label={LL.PHY_TYPE()}
disabled={saving}
value={data.phy_type} value={data.phy_type}
fullWidth fullWidth
variant="outlined" variant="outlined"
@@ -302,7 +594,6 @@ const ApplicationSettings: FC = () => {
type="number" type="number"
onChange={updateFormValue} onChange={updateFormValue}
margin="normal" margin="normal"
disabled={saving}
/> />
</Grid> </Grid>
<Grid item xs={12} sm={6} md={4}> <Grid item xs={12} sm={6} md={4}>
@@ -315,14 +606,12 @@ const ApplicationSettings: FC = () => {
type="number" type="number"
onChange={updateFormValue} onChange={updateFormValue}
margin="normal" margin="normal"
disabled={saving}
/> />
</Grid> </Grid>
<Grid item xs={12} sm={6} md={4}> <Grid item xs={12} sm={6} md={4}>
<TextField <TextField
name="eth_clock_mode" name="eth_clock_mode"
label="PHY Clk" label="PHY Clk"
disabled={saving}
value={data.eth_clock_mode} value={data.eth_clock_mode}
fullWidth fullWidth
variant="outlined" variant="outlined"
@@ -340,9 +629,6 @@ const ApplicationSettings: FC = () => {
)} )}
</> </>
)} )}
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
{LL.SETTINGS_OF(LL.EMS_BUS(0))}
</Typography>
<Grid <Grid
container container
spacing={1} spacing={1}
@@ -354,7 +640,6 @@ const ApplicationSettings: FC = () => {
<TextField <TextField
name="tx_mode" name="tx_mode"
label={LL.TX_MODE()} label={LL.TX_MODE()}
disabled={saving}
value={data.tx_mode} value={data.tx_mode}
fullWidth fullWidth
variant="outlined" variant="outlined"
@@ -371,8 +656,7 @@ const ApplicationSettings: FC = () => {
<Grid item xs={12} sm={6}> <Grid item xs={12} sm={6}>
<TextField <TextField
name="ems_bus_id" name="ems_bus_id"
label={LL.ID_OF(LL.EMS_BUS(1))} label={LL.ID_OF(LL.EMS_BUS(0))}
disabled={saving}
value={data.ems_bus_id} value={data.ems_bus_id}
fullWidth fullWidth
variant="outlined" variant="outlined"
@@ -394,33 +678,16 @@ const ApplicationSettings: FC = () => {
</TextField> </TextField>
</Grid> </Grid>
</Grid> </Grid>
<Typography sx={{ pt: 2 }} variant="h6" color="primary"> <BlockFormControlLabel
{LL.GENERAL_OPTIONS()} control={
</Typography> <Checkbox
<Grid item> checked={data.readonly_mode}
<TextField
name="locale"
label={LL.LANGUAGE_ENTITIES()}
disabled={saving}
value={data.locale}
fullWidth
variant="outlined"
onChange={updateFormValue} onChange={updateFormValue}
margin="normal" name="readonly_mode"
select />
> }
<MenuItem value="de">Deutsch (DE)</MenuItem> label={LL.READONLY()}
<MenuItem value="en">English (EN)</MenuItem> />
<MenuItem value="fr">Français (FR)</MenuItem>
<MenuItem value="it">Italiano (IT)</MenuItem>
<MenuItem value="nl">Nederlands (NL)</MenuItem>
<MenuItem value="no">Norsk (NO)</MenuItem>
<MenuItem value="pl">Polski (PL)</MenuItem>
<MenuItem value="sk">Slovenčina (SK)</MenuItem>
<MenuItem value="sv">Svenska (SV)</MenuItem>
<MenuItem value="tr">Türk (TR)</MenuItem>
</TextField>
</Grid>
{data.led_gpio !== 0 && ( {data.led_gpio !== 0 && (
<BlockFormControlLabel <BlockFormControlLabel
control={ control={
@@ -431,64 +698,8 @@ const ApplicationSettings: FC = () => {
/> />
} }
label={LL.HIDE_LED()} label={LL.HIDE_LED()}
disabled={saving}
/> />
)} )}
<BlockFormControlLabel
control={
<Checkbox
checked={data.telnet_enabled}
onChange={updateFormValue}
name="telnet_enabled"
/>
}
label={LL.ENABLE_TELNET()}
disabled={saving}
/>
<BlockFormControlLabel
control={
<Checkbox
checked={data.analog_enabled}
onChange={updateFormValue}
name="analog_enabled"
/>
}
label={LL.ENABLE_ANALOG()}
disabled={saving}
/>
<BlockFormControlLabel
control={
<Checkbox
checked={data.fahrenheit}
onChange={updateFormValue}
name="fahrenheit"
/>
}
label={LL.CONVERT_FAHRENHEIT()}
disabled={saving}
/>
<BlockFormControlLabel
control={
<Checkbox
checked={data.notoken_api}
onChange={updateFormValue}
name="notoken_api"
/>
}
label={LL.BYPASS_TOKEN()}
disabled={saving}
/>
<BlockFormControlLabel
control={
<Checkbox
checked={data.readonly_mode}
onChange={updateFormValue}
name="readonly_mode"
/>
}
label={LL.READONLY()}
disabled={saving}
/>
<BlockFormControlLabel <BlockFormControlLabel
control={ control={
<Checkbox <Checkbox
@@ -498,8 +709,11 @@ const ApplicationSettings: FC = () => {
/> />
} }
label={LL.UNDERCLOCK_CPU()} label={LL.UNDERCLOCK_CPU()}
disabled={saving}
/> />
<Typography sx={{ pb: 1, pt: 2 }} variant="h6" color="primary">
{LL.GENERAL_OPTIONS()}
</Typography>
<BlockFormControlLabel <BlockFormControlLabel
control={ control={
<Checkbox <Checkbox
@@ -509,16 +723,8 @@ const ApplicationSettings: FC = () => {
/> />
} }
label={LL.HEATINGOFF()} label={LL.HEATINGOFF()}
disabled={saving}
/> />
<Grid
container
spacing={1}
direction="row"
justifyContent="flex-start"
alignItems="flex-start"
>
<Grid item xs={12} sm={6} md={4}>
<BlockFormControlLabel <BlockFormControlLabel
control={ control={
<Checkbox <Checkbox
@@ -529,9 +735,8 @@ const ApplicationSettings: FC = () => {
} }
label={LL.REMOTE_TIMEOUT_EN()} label={LL.REMOTE_TIMEOUT_EN()}
/> />
</Grid>
{data.remote_timeout_en && ( {data.remote_timeout_en && (
<Grid item xs={12} sm={6} md={4}> <Box mt={2}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="remote_timeout" name="remote_timeout"
@@ -541,15 +746,13 @@ const ApplicationSettings: FC = () => {
<InputAdornment position="end">{LL.HOURS()}</InputAdornment> <InputAdornment position="end">{LL.HOURS()}</InputAdornment>
) )
}} }}
fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.remote_timeout)} value={numberValue(data.remote_timeout)}
type="number" type="number"
onChange={updateFormValue} onChange={updateFormValue}
/> />
</Grid> </Box>
)} )}
</Grid>
<Grid <Grid
container container
spacing={0} spacing={0}
@@ -566,7 +769,6 @@ const ApplicationSettings: FC = () => {
/> />
} }
label={LL.ENABLE_SHOWER_TIMER()} label={LL.ENABLE_SHOWER_TIMER()}
disabled={saving}
/> />
<BlockFormControlLabel <BlockFormControlLabel
control={ control={
@@ -649,256 +851,7 @@ const ApplicationSettings: FC = () => {
</> </>
)} )}
</Grid> </Grid>
<Typography sx={{ pt: 3 }} variant="h6" color="primary">
{LL.FORMATTING_OPTIONS()}
</Typography>
<Grid
container
spacing={1}
direction="row"
justifyContent="flex-start"
alignItems="flex-start"
>
<Grid item xs={12} sm={6} md={4}>
<TextField
name="bool_dashboard"
label={LL.BOOLEAN_FORMAT_DASHBOARD()}
value={data.bool_dashboard}
fullWidth
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={1}>{LL.ONOFF()}</MenuItem>
<MenuItem value={2}>{LL.ONOFF_CAP()}</MenuItem>
<MenuItem value={3}>true/false</MenuItem>
<MenuItem value={5}>1/0</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<TextField
name="bool_format"
label={LL.BOOLEAN_FORMAT_API()}
value={data.bool_format}
fullWidth
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={1}>{LL.ONOFF()}</MenuItem>
<MenuItem value={2}>{LL.ONOFF_CAP()}</MenuItem>
<MenuItem value={3}>&quot;true&quot;/&quot;false&quot;</MenuItem>
<MenuItem value={4}>true/false</MenuItem>
<MenuItem value={5}>&quot;1&quot;/&quot;0&quot;</MenuItem>
<MenuItem value={6}>1/0</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<TextField
name="enum_format"
label={LL.ENUM_FORMAT()}
value={data.enum_format}
fullWidth
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={1}>{LL.VALUE(5)}</MenuItem>
<MenuItem value={2}>{LL.INDEX()}</MenuItem>
</TextField>
</Grid>
</Grid>
{data.dallas_gpio !== 0 && (
<>
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
{LL.TEMP_SENSORS()}
</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.dallas_parasite}
onChange={updateFormValue}
name="dallas_parasite"
/>
}
label={LL.ENABLE_PARASITE()}
disabled={saving}
/>
</>
)}
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
{LL.LOGGING()}
</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.trace_raw}
onChange={updateFormValue}
name="trace_raw"
/>
}
label={LL.LOG_HEX()}
disabled={saving}
/>
<BlockFormControlLabel
control={
<Checkbox
checked={data.syslog_enabled}
onChange={updateFormValue}
name="syslog_enabled"
disabled={saving}
/>
}
label={LL.ENABLE_SYSLOG()}
/>
{data.syslog_enabled && (
<Grid
container
spacing={1}
direction="row"
justifyContent="flex-start"
alignItems="flex-start"
>
<Grid item xs={12} sm={6}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="syslog_host"
label="Host"
fullWidth
variant="outlined"
value={data.syslog_host}
onChange={updateFormValue}
margin="normal"
disabled={saving}
/>
</Grid>
<Grid item xs={12} sm={6}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="syslog_port"
label="Port"
fullWidth
variant="outlined"
value={numberValue(data.syslog_port)}
type="number"
onChange={updateFormValue}
margin="normal"
disabled={saving}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
name="syslog_level"
label={LL.LOG_LEVEL()}
value={data.syslog_level}
fullWidth
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
disabled={saving}
>
<MenuItem value={-1}>OFF</MenuItem>
<MenuItem value={3}>ERR</MenuItem>
<MenuItem value={5}>NOTICE</MenuItem>
<MenuItem value={6}>INFO</MenuItem>
<MenuItem value={7}>DEBUG</MenuItem>
<MenuItem value={9}>ALL</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={6}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="syslog_mark_interval"
label={LL.MARK_INTERVAL()}
InputProps={{
endAdornment: (
<InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
)
}}
fullWidth
variant="outlined"
value={numberValue(data.syslog_mark_interval)}
type="number"
onChange={updateFormValue}
margin="normal"
disabled={saving}
/>
</Grid>
</Grid>
)}
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
Modbus
</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.modbus_enabled}
onChange={updateFormValue}
name="modbus_enabled"
disabled={!hardwareData.psram}
/>
}
label={LL.ENABLE_MODBUS()}
/>
{data.modbus_enabled && (
<Grid
container
spacing={1}
direction="row"
justifyContent="flex-start"
alignItems="flex-start"
>
<Grid item xs={12} sm={6}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="modbus_max_clients"
label={LL.AP_MAX_CLIENTS()}
fullWidth
variant="outlined"
value={numberValue(data.modbus_max_clients)}
type="number"
onChange={updateFormValue}
margin="normal"
disabled={saving}
/>
</Grid>
<Grid item xs={12} sm={6}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="modbus_port"
label="Port"
fullWidth
variant="outlined"
value={numberValue(data.modbus_port)}
type="number"
onChange={updateFormValue}
margin="normal"
disabled={saving}
/>
</Grid>
<Grid item xs={12} sm={6}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="modbus_timeout"
label="Timeout"
InputProps={{
endAdornment: <InputAdornment position="end">ms</InputAdornment>
}}
fullWidth
variant="outlined"
value={numberValue(data.modbus_timeout)}
type="number"
onChange={updateFormValue}
margin="normal"
disabled={saving}
/>
</Grid>
</Grid>
)}
{restartNeeded && ( {restartNeeded && (
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}> <MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
<Button <Button
@@ -915,7 +868,6 @@ const ApplicationSettings: FC = () => {
<ButtonRow> <ButtonRow>
<Button <Button
startIcon={<CancelIcon />} startIcon={<CancelIcon />}
disabled={saving}
variant="outlined" variant="outlined"
color="primary" color="primary"
type="submit" type="submit"
@@ -925,7 +877,6 @@ const ApplicationSettings: FC = () => {
</Button> </Button>
<Button <Button
startIcon={<WarningIcon color="warning" />} startIcon={<WarningIcon color="warning" />}
disabled={saving}
variant="contained" variant="contained"
color="info" color="info"
type="submit" type="submit"

View File

@@ -0,0 +1,295 @@
import { toast } from 'react-toastify';
import DownloadIcon from '@mui/icons-material/GetApp';
import { Box, Button, Divider, Link, Typography } from '@mui/material';
import * as SystemApi from 'api/system';
import {
API,
getCustomizations,
getEntities,
getSchedule,
getSettings
} from 'api/app';
import { getDevVersion, getStableVersion } from 'api/system';
import { useRequest } from 'alova/client';
import type { APIcall } from 'app/main/types';
import {
FormLoader,
SectionContent,
SingleUpload,
useLayoutTitle
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
const DownloadUpload = () => {
const { LL } = useI18nContext();
const { send: sendSettings } = useRequest(getSettings(), {
immediate: false
}).onSuccess((event) => {
saveFile(event.data, 'settings.json');
});
const { send: sendCustomizations } = useRequest(getCustomizations(), {
immediate: false
}).onSuccess((event) => {
saveFile(event.data, 'customizations.json');
});
const { send: sendEntities } = useRequest(getEntities(), {
immediate: false
}).onSuccess((event) => {
saveFile(event.data, 'entities.json');
});
const { send: sendSchedule } = useRequest(getSchedule(), {
immediate: false
}).onSuccess((event) => {
saveFile(event.data, 'schedule.json');
});
const { send: getAPI } = useRequest((data: APIcall) => API(data), {
immediate: false
}).onSuccess((event) => {
saveFile(
event.data,
String(event.args[0].device) + '_' + String(event.args[0].entity) + '.txt'
);
});
const {
data: data,
send: loadData,
error
} = useRequest(SystemApi.readHardwareStatus);
// called immediately to get the latest version, on page load
// set immediate to false to avoid calling the API on page load and GH blocking while testing!
const { data: latestVersion } = useRequest(getStableVersion, {
immediate: true
});
const { data: latestDevVersion } = useRequest(getDevVersion, {
immediate: true
});
const STABLE_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/';
const STABLE_RELNOTES_URL =
'https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md';
const DEV_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/latest/';
const DEV_RELNOTES_URL =
'https://github.com/emsesp/EMS-ESP32/blob/dev/CHANGELOG_LATEST.md';
const getBinURL = (v: string) =>
'EMS-ESP-' +
v.replaceAll('.', '_') +
'-' +
getPlatform().replaceAll('-', '_') +
'.bin';
const getPlatform = () => {
if (
data.flash_chip_size >= 16384 &&
data.esp_platform === 'ESP32' &&
data.psram
) {
return data.esp_platform + '-16M';
}
return data.esp_platform;
};
const saveFile = (json: unknown, endpoint: string) => {
const anchor = document.createElement('a');
anchor.href = URL.createObjectURL(
new Blob([JSON.stringify(json, null, 2)], {
type: 'text/plain'
})
);
anchor.download = 'emsesp_' + endpoint;
anchor.click();
URL.revokeObjectURL(anchor.href);
toast.info(LL.DOWNLOAD_SUCCESSFUL());
};
const downloadSettings = async () => {
await sendSettings().catch((error: Error) => {
toast.error(error.message);
});
};
const downloadCustomizations = async () => {
await sendCustomizations().catch((error: Error) => {
toast.error(error.message);
});
};
const downloadEntities = async () => {
await sendEntities().catch((error: Error) => {
toast.error(error.message);
});
};
const downloadSchedule = async () => {
await sendSchedule().catch((error: Error) => {
toast.error(error.message);
});
};
const callAPI = async (device: string, entity: string) => {
await getAPI({ device, entity, id: 0 }).catch((error: Error) => {
toast.error(error.message);
});
};
useLayoutTitle(LL.DOWNLOAD_UPLOAD());
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (
<>
<Typography sx={{ pb: 2 }} variant="h6" color="primary">
{LL.DOWNLOAD(0)}
</Typography>
<Box color="warning.main">
<Typography mb={1} variant="body2">
{LL.HELP_INFORMATION_4()}
</Typography>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => callAPI('system', 'info')}
>
{LL.SUPPORT_INFORMATION(0)}
</Button>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => callAPI('system', 'allvalues')}
>
{LL.ALLVALUES()}
</Button>
</Box>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_SETTINGS_TEXT()}
</Typography>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={downloadSettings}
>
{LL.SETTINGS_OF('')}
</Button>
</Box>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}
</Typography>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={downloadCustomizations}
>
{LL.CUSTOMIZATIONS()}
</Button>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={downloadEntities}
>
{LL.CUSTOM_ENTITIES(0)}
</Button>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_SCHEDULE_TEXT()}
</Typography>
</Box>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={downloadSchedule}
>
{LL.SCHEDULE(0)}
</Button>
</Box>
<Box color="warning.main">
<Typography mt={2} variant="body2">
{LL.EMS_ESP_VER()}
</Typography>
</Box>
<Box p={2} mt={2} border="1px solid grey" borderRadius={2}>
{LL.VERSION_ON() + ' '}
<b>{data.emsesp_version}</b>&nbsp;({getPlatform()})
<Divider />
{latestVersion && (
<Box mt={2}>
{LL.THE_LATEST()}&nbsp;{LL.OFFICIAL()}&nbsp;{LL.RELEASE_IS()}
&nbsp;<b>{latestVersion}</b>
&nbsp;(
<Link target="_blank" href={STABLE_RELNOTES_URL} color="primary">
{LL.RELEASE_NOTES()}
</Link>
)&nbsp;(
<Link
target="_blank"
href={
STABLE_URL + 'v' + latestVersion + '/' + getBinURL(latestVersion)
}
color="primary"
>
{LL.DOWNLOAD(1)}
</Link>
)
</Box>
)}
{latestDevVersion && (
<Box mt={2}>
{LL.THE_LATEST()}&nbsp;{LL.DEVELOPMENT()}&nbsp;{LL.RELEASE_IS()}
&nbsp;
<b>{latestDevVersion}</b>
&nbsp;(
<Link target="_blank" href={DEV_RELNOTES_URL} color="primary">
{LL.RELEASE_NOTES()}
</Link>
)&nbsp;(
<Link
target="_blank"
href={DEV_URL + getBinURL(latestDevVersion)}
color="primary"
>
{LL.DOWNLOAD(1)}
</Link>
)
</Box>
)}
</Box>
<SingleUpload />
</>
);
};
return <SectionContent>{content()}</SectionContent>;
};
export default DownloadUpload;

View File

@@ -1,5 +1,4 @@
import { useState } from 'react'; import { useState } from 'react';
import type { FC } from 'react';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
@@ -31,7 +30,7 @@ import type { MqttSettingsType } from 'types';
import { numberValue, updateValueDirty, useRest } from 'utils'; import { numberValue, updateValueDirty, useRest } from 'utils';
import { createMqttSettingsValidator, validate } from 'validators'; import { createMqttSettingsValidator, validate } from 'validators';
const MqttSettings: FC = () => { const MqttSettings = () => {
const { const {
loadData, loadData,
saving, saving,

View File

@@ -1,13 +1,13 @@
import { useState } from 'react'; import { useState } from 'react';
import type { FC } from 'react';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { Button, Checkbox, MenuItem } from '@mui/material'; import { Button, Checkbox, MenuItem } from '@mui/material';
import * as NTPApi from 'api/ntp'; import * as NTPApi from 'api/ntp';
import { readNTPSettings } from 'api/ntp';
import { updateState } from 'alova'; import { updateState } from 'alova/client';
import type { ValidateFieldsError } from 'async-validator'; import type { ValidateFieldsError } from 'async-validator';
import { import {
BlockFormControlLabel, BlockFormControlLabel,
@@ -26,7 +26,7 @@ import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
import { TIME_ZONES, selectedTimeZone, timeZoneSelectItems } from './TZ'; import { TIME_ZONES, selectedTimeZone, timeZoneSelectItems } from './TZ';
const NTPSettings: FC = () => { const NTPSettings = () => {
const { const {
loadData, loadData,
saving, saving,
@@ -72,8 +72,7 @@ const NTPSettings: FC = () => {
const changeTimeZone = (event: React.ChangeEvent<HTMLInputElement>) => { const changeTimeZone = (event: React.ChangeEvent<HTMLInputElement>) => {
updateFormValue(event); updateFormValue(event);
void updateState(readNTPSettings(), (settings: NTPSettingsType) => ({
updateState('ntpSettings', (settings: NTPSettingsType) => ({
...settings, ...settings,
tz_label: event.target.value, tz_label: event.target.value,
tz_format: TIME_ZONES[event.target.value] tz_format: TIME_ZONES[event.target.value]

View File

@@ -1,4 +1,4 @@
import { type FC, useState } from 'react'; import { useState } from 'react';
import AccessTimeIcon from '@mui/icons-material/AccessTime'; import AccessTimeIcon from '@mui/icons-material/AccessTime';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
@@ -24,12 +24,12 @@ import {
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { dialogStyle } from 'CustomTheme'; import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova'; import { useRequest } from 'alova/client';
import { SectionContent, useLayoutTitle } from 'components'; import { SectionContent, useLayoutTitle } from 'components';
import ListMenuItem from 'components/layout/ListMenuItem'; import ListMenuItem from 'components/layout/ListMenuItem';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
const Settings: FC = () => { const Settings = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle(LL.SETTINGS(0)); useLayoutTitle(LL.SETTINGS(0));
@@ -136,8 +136,8 @@ const Settings: FC = () => {
<ListMenuItem <ListMenuItem
icon={ImportExportIcon} icon={ImportExportIcon}
bgcolor="#5d89f7" bgcolor="#5d89f7"
label={LL.UPLOAD_DOWNLOAD()} label={LL.DOWNLOAD_UPLOAD()}
text={LL.UPLOAD_DOWNLOAD_1()} text={LL.DOWNLOAD_UPLOAD_1()}
to="upload" to="upload"
/> />
</List> </List>

View File

@@ -1,367 +0,0 @@
import { type FC, useState } from 'react';
import { toast } from 'react-toastify';
import DownloadIcon from '@mui/icons-material/GetApp';
import { Box, Button, Divider, Link, Typography } from '@mui/material';
import * as SystemApi from 'api/system';
import * as EMSESP from 'app/main/api';
import { useRequest } from 'alova';
import type { APIcall } from 'app/main/types';
import {
FormLoader,
SectionContent,
SingleUpload,
useLayoutTitle
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import RestartMonitor from '../status/RestartMonitor';
const UploadDownload: FC = () => {
const { LL } = useI18nContext();
const [restarting, setRestarting] = useState<boolean>();
const [md5, setMd5] = useState<string>();
const { send: getSettings, onSuccess: onSuccessGetSettings } = useRequest(
EMSESP.getSettings(),
{
immediate: false
}
);
const { send: getCustomizations, onSuccess: onSuccessGetCustomizations } =
useRequest(EMSESP.getCustomizations(), {
immediate: false
});
const { send: getEntities, onSuccess: onSuccessGetEntities } = useRequest(
EMSESP.getEntities(),
{
immediate: false
}
);
const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest(
EMSESP.getSchedule(),
{
immediate: false
}
);
const { send: getAPI, onSuccess: onGetAPI } = useRequest(
(data: APIcall) => EMSESP.API(data),
{
immediate: false
}
);
const {
data: data,
send: loadData,
error
} = useRequest(SystemApi.readHardwareStatus, { force: true });
const { data: latestVersion } = useRequest(SystemApi.getStableVersion, {
immediate: true,
force: true
});
const { data: latestDevVersion } = useRequest(SystemApi.getDevVersion, {
immediate: true,
force: true
});
const STABLE_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/';
const DEV_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/latest/';
const STABLE_RELNOTES_URL =
'https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md';
const DEV_RELNOTES_URL =
'https://github.com/emsesp/EMS-ESP32/blob/dev/CHANGELOG_LATEST.md';
const getBinURL = (v: string) =>
'EMS-ESP-' +
v.replaceAll('.', '_') +
'-' +
getPlatform().replaceAll('-', '_') +
'.bin';
const getPlatform = () => {
if (data.flash_chip_size === 16384) {
return data.esp_platform + '-16M';
}
return data.esp_platform;
};
const {
loading: isUploading,
uploading: progress,
send: sendUpload,
onSuccess: onSuccessUpload,
abort: cancelUpload
} = useRequest(SystemApi.uploadFile, {
immediate: false,
force: true
});
onSuccessUpload(({ data }) => {
if (data) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
setMd5(data.md5);
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
} else {
setRestarting(true);
}
});
const startUpload = async (files: File[]) => {
await sendUpload(files[0]).catch((error: Error) => {
if (error.message === 'The user aborted a request') {
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
} else if (error.message === 'Network Error') {
toast.warning('Invalid file extension or incompatible bin file');
} else {
toast.error(error.message);
}
});
};
const saveFile = (json: unknown, endpoint: string) => {
const anchor = document.createElement('a');
anchor.href = URL.createObjectURL(
new Blob([JSON.stringify(json, null, 2)], {
type: 'text/plain'
})
);
anchor.download = 'emsesp_' + endpoint;
anchor.click();
URL.revokeObjectURL(anchor.href);
toast.info(LL.DOWNLOAD_SUCCESSFUL());
};
onSuccessGetSettings((event) => {
saveFile(event.data, 'settings.json');
});
onSuccessGetCustomizations((event) => {
saveFile(event.data, 'customizations.json');
});
onSuccessGetEntities((event) => {
saveFile(event.data, 'entities.json');
});
onSuccessGetSchedule((event) => {
saveFile(event.data, 'schedule.json');
});
onGetAPI((event) => {
saveFile(
event.data,
event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt'
);
});
const downloadSettings = async () => {
await getSettings().catch((error: Error) => {
toast.error(error.message);
});
};
const downloadCustomizations = async () => {
await getCustomizations().catch((error: Error) => {
toast.error(error.message);
});
};
const downloadEntities = async () => {
await getEntities().catch((error: Error) => {
toast.error(error.message);
});
};
const downloadSchedule = async () => {
await getSchedule().catch((error: Error) => {
toast.error(error.message);
});
};
const callAPI = async (device: string, entity: string) => {
await getAPI({ device, entity, id: 0 }).catch((error: Error) => {
toast.error(error.message);
});
};
useLayoutTitle(LL.UPLOAD_DOWNLOAD());
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (
<>
<Typography sx={{ pb: 2 }} variant="h6" color="primary">
{LL.EMS_ESP_VER()}
</Typography>
<Box p={2} border="2px solid grey" borderRadius={2}>
{LL.VERSION_ON() + ' '}
<b>{data.emsesp_version}</b>&nbsp;({getPlatform()})
<Divider />
{latestVersion && (
<Box mt={2}>
{LL.THE_LATEST()}&nbsp;{LL.OFFICIAL()}&nbsp;{LL.RELEASE_IS()}
&nbsp;<b>{latestVersion}</b>
&nbsp;(
<Link target="_blank" href={STABLE_RELNOTES_URL} color="primary">
{LL.RELEASE_NOTES()}
</Link>
)&nbsp;(
<Link
target="_blank"
href={
STABLE_URL +
'v' +
latestVersion +
'/' +
getBinURL(latestVersion as string)
}
color="primary"
>
{LL.DOWNLOAD(1)}
</Link>
)
</Box>
)}
{latestDevVersion && (
<Box mt={2}>
{LL.THE_LATEST()}&nbsp;{LL.DEVELOPMENT()}&nbsp;{LL.RELEASE_IS()}
&nbsp;
<b>{latestDevVersion}</b>
&nbsp;(
<Link target="_blank" href={DEV_RELNOTES_URL} color="primary">
{LL.RELEASE_NOTES()}
</Link>
)&nbsp;(
<Link
target="_blank"
href={DEV_URL + getBinURL(latestDevVersion as string)}
color="primary"
>
{LL.DOWNLOAD(1)}
</Link>
)
</Box>
)}
</Box>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.UPLOAD()}
</Typography>
<Box mb={2} color="warning.main">
<Typography variant="body2">
{LL.UPLOAD_TEXT()}
<br />
<br />
{LL.RESTART_TEXT(1)}.
</Typography>
</Box>
{md5 && (
<Box mb={2}>
<Typography variant="body2">{'MD5: ' + md5}</Typography>
</Box>
)}
<SingleUpload
onDrop={startUpload}
onCancel={cancelUpload}
isUploading={isUploading}
progress={progress}
/>
{!isUploading && (
<>
<Typography sx={{ pt: 4, pb: 2 }} variant="h6" color="primary">
{LL.DOWNLOAD(0)}
</Typography>
<Box color="warning.main">
<Typography mb={1} variant="body2">
{LL.HELP_INFORMATION_4()}
</Typography>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => callAPI('system', 'info')}
>
{LL.SUPPORT_INFORMATION(0)}
</Button>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => callAPI('system', 'allvalues')}
>
All Values
</Button>
</Box>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_SETTINGS_TEXT()}
</Typography>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={downloadSettings}
>
{LL.SETTINGS_OF('')}
</Button>
</Box>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}
</Typography>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={downloadCustomizations}
>
{LL.CUSTOMIZATIONS()}
</Button>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={downloadEntities}
>
{LL.CUSTOM_ENTITIES(0)}
</Button>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_SCHEDULE_TEXT()}
</Typography>
</Box>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={downloadSchedule}
>
{LL.SCHEDULE(0)}
</Button>
</Box>
</>
)}
</>
);
};
return (
<SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>
);
};
export default UploadDownload;

View File

@@ -1,5 +1,4 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import type { FC } from 'react';
import { Navigate, Route, Routes, useNavigate } from 'react-router-dom'; import { Navigate, Route, Routes, useNavigate } from 'react-router-dom';
import { Tab } from '@mui/material'; import { Tab } from '@mui/material';
@@ -12,7 +11,7 @@ import NetworkSettings from './NetworkSettings';
import { WiFiConnectionContext } from './WiFiConnectionContext'; import { WiFiConnectionContext } from './WiFiConnectionContext';
import WiFiNetworkScanner from './WiFiNetworkScanner'; import WiFiNetworkScanner from './WiFiNetworkScanner';
const Network: FC = () => { const Network = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle(LL.SETTINGS_OF(LL.NETWORK(0))); useLayoutTitle(LL.SETTINGS_OF(LL.NETWORK(0)));

View File

@@ -1,5 +1,4 @@
import { useContext, useEffect, useState } from 'react'; import { useContext, useEffect, useState } from 'react';
import type { FC } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
@@ -26,7 +25,7 @@ import {
import * as NetworkApi from 'api/network'; import * as NetworkApi from 'api/network';
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { updateState, useRequest } from 'alova'; import { updateState, useRequest } from 'alova/client';
import type { ValidateFieldsError } from 'async-validator'; import type { ValidateFieldsError } from 'async-validator';
import { import {
BlockFormControlLabel, BlockFormControlLabel,
@@ -48,7 +47,7 @@ import RestartMonitor from '../../status/RestartMonitor';
import { WiFiConnectionContext } from './WiFiConnectionContext'; import { WiFiConnectionContext } from './WiFiConnectionContext';
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector'; import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
const NetworkSettings: FC = () => { const NetworkSettings = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const { selectedNetwork, deselectNetwork } = useContext(WiFiConnectionContext); const { selectedNetwork, deselectNetwork } = useContext(WiFiConnectionContext);
@@ -80,7 +79,9 @@ const NetworkSettings: FC = () => {
useEffect(() => { useEffect(() => {
if (!initialized && data) { if (!initialized && data) {
if (selectedNetwork) { if (selectedNetwork) {
updateState('networkSettings', (current_data: NetworkSettingsType) => ({ void updateState(
NetworkApi.readNetworkSettings(),
(current_data: NetworkSettingsType) => ({
ssid: selectedNetwork.ssid, ssid: selectedNetwork.ssid,
bssid: selectedNetwork.bssid, bssid: selectedNetwork.bssid,
password: current_data ? current_data.password : '', password: current_data ? current_data.password : '',
@@ -92,7 +93,8 @@ const NetworkSettings: FC = () => {
enableMDNS: true, enableMDNS: true,
enableCORS: false, enableCORS: false,
CORSOrigin: '*' CORSOrigin: '*'
})); })
);
} }
setInitialized(true); setInitialized(true);
} }

View File

@@ -1,12 +1,11 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import type { FC } from 'react';
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi'; import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
import * as NetworkApi from 'api/network'; import * as NetworkApi from 'api/network';
import { updateState, useRequest } from 'alova'; import { updateState, useRequest } from 'alova/client';
import { ButtonRow, FormLoader, SectionContent } from 'components'; import { ButtonRow, FormLoader, SectionContent } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
@@ -15,23 +14,28 @@ import WiFiNetworkSelector from './WiFiNetworkSelector';
const NUM_POLLS = 10; const NUM_POLLS = 10;
const POLLING_FREQUENCY = 1000; const POLLING_FREQUENCY = 1000;
const WiFiNetworkScanner: FC = () => { const WiFiNetworkScanner = () => {
const pollCount = useRef(0); const pollCount = useRef(0);
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [errorMessage, setErrorMessage] = useState<string>(); const [errorMessage, setErrorMessage] = useState<string>();
const { send: scanNetworks, onComplete: onCompleteScanNetworks } = useRequest( // is called on page load to start network scan
NetworkApi.scanNetworks const { send: scanNetworks } = useRequest(NetworkApi.scanNetworks).onComplete(
); // is called on page load to start network scan () => {
const { pollCount.current = 0;
data: networkList, setErrorMessage(undefined);
send: getNetworkList, void updateState(NetworkApi.listNetworks(), () => undefined);
onSuccess: onSuccessNetworkList void getNetworkList();
} = useRequest(NetworkApi.listNetworks, { }
immediate: false );
});
onSuccessNetworkList((event) => { const { data: networkList, send: getNetworkList } = useRequest(
NetworkApi.listNetworks,
{
immediate: false
}
).onSuccess((event) => {
// is called when network scan is completed
if (!event.data) { if (!event.data) {
const completedPollCount = pollCount.current + 1; const completedPollCount = pollCount.current + 1;
if (completedPollCount < NUM_POLLS) { if (completedPollCount < NUM_POLLS) {
@@ -44,13 +48,6 @@ const WiFiNetworkScanner: FC = () => {
} }
}); });
onCompleteScanNetworks(() => {
pollCount.current = 0;
setErrorMessage(undefined);
updateState('listNetworks', () => undefined);
void getNetworkList();
});
const renderNetworkScanner = () => { const renderNetworkScanner = () => {
if (!networkList) { if (!networkList) {
return ( return (

View File

@@ -1,5 +1,4 @@
import { useContext } from 'react'; import { useContext } from 'react';
import type { FC } from 'react';
import LockIcon from '@mui/icons-material/Lock'; import LockIcon from '@mui/icons-material/Lock';
import LockOpenIcon from '@mui/icons-material/LockOpen'; import LockOpenIcon from '@mui/icons-material/LockOpen';
@@ -23,10 +22,6 @@ import { WiFiEncryptionType } from 'types';
import { WiFiConnectionContext } from './WiFiConnectionContext'; import { WiFiConnectionContext } from './WiFiConnectionContext';
interface WiFiNetworkSelectorProps {
networkList: WiFiNetworkList;
}
export const isNetworkOpen = ({ encryption_type }: WiFiNetwork) => export const isNetworkOpen = ({ encryption_type }: WiFiNetwork) =>
encryption_type === WiFiEncryptionType.WIFI_AUTH_OPEN; encryption_type === WiFiEncryptionType.WIFI_AUTH_OPEN;
@@ -62,7 +57,7 @@ const networkQualityHighlight = ({ rssi }: WiFiNetwork, theme: Theme) => {
return theme.palette.success.main; return theme.palette.success.main;
}; };
const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => { const WiFiNetworkSelector = ({ networkList }: { networkList: WiFiNetworkList }) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const theme = useTheme(); const theme = useTheme();

View File

@@ -1,5 +1,4 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import type { FC } from 'react';
import CloseIcon from '@mui/icons-material/Close'; import CloseIcon from '@mui/icons-material/Close';
import { import {
@@ -17,7 +16,7 @@ import {
import * as SecurityApi from 'api/security'; import * as SecurityApi from 'api/security';
import { dialogStyle } from 'CustomTheme'; import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova'; import { useRequest } from 'alova/client';
import { MessageBox } from 'components'; import { MessageBox } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
@@ -26,7 +25,7 @@ interface GenerateTokenProps {
onClose: () => void; onClose: () => void;
} }
const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => { const GenerateToken = ({ username, onClose }: GenerateTokenProps) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const open = !!username; const open = !!username;

View File

@@ -1,5 +1,4 @@
import { useContext, useState } from 'react'; import { useContext, useState } from 'react';
import type { FC } from 'react';
import { useBlocker } from 'react-router-dom'; import { useBlocker } from 'react-router-dom';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
@@ -40,7 +39,7 @@ import { createUserValidator } from 'validators';
import GenerateToken from './GenerateToken'; import GenerateToken from './GenerateToken';
import User from './User'; import User from './User';
const ManageUsers: FC = () => { const ManageUsers = () => {
const { loadData, saveData, saving, data, updateDataValue, errorMessage } = const { loadData, saveData, saving, data, updateDataValue, errorMessage } =
useRest<SecuritySettingsType>({ useRest<SecuritySettingsType>({
read: SecurityApi.readSecuritySettings, read: SecurityApi.readSecuritySettings,
@@ -221,8 +220,8 @@ const ManageUsers: FC = () => {
)} )}
<Box display="flex" flexWrap="wrap"> <Box display="flex" flexWrap="wrap">
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
{changed !== 0 && ( {changed !== 0 && (
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
<ButtonRow> <ButtonRow>
<Button <Button
startIcon={<CancelIcon />} startIcon={<CancelIcon />}
@@ -245,9 +244,8 @@ const ManageUsers: FC = () => {
{LL.APPLY_CHANGES(changed)} {LL.APPLY_CHANGES(changed)}
</Button> </Button>
</ButtonRow> </ButtonRow>
)}
</Box> </Box>
)}
<Box flexWrap="nowrap" whiteSpace="nowrap"> <Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow> <ButtonRow>
<Button <Button

View File

@@ -1,4 +1,3 @@
import type { FC } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom'; import { Navigate, Route, Routes } from 'react-router-dom';
import { Tab } from '@mui/material'; import { Tab } from '@mui/material';
@@ -9,7 +8,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import ManageUsers from './ManageUsers'; import ManageUsers from './ManageUsers';
import SecuritySettings from './SecuritySettings'; import SecuritySettings from './SecuritySettings';
const Security: FC = () => { const Security = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle(LL.SETTINGS_OF(LL.SECURITY(0))); useLayoutTitle(LL.SETTINGS_OF(LL.SECURITY(0)));

View File

@@ -1,5 +1,4 @@
import { useContext, useState } from 'react'; import { useContext, useState } from 'react';
import type { FC } from 'react';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
@@ -22,7 +21,7 @@ import type { SecuritySettingsType } from 'types';
import { updateValueDirty, useRest } from 'utils'; import { updateValueDirty, useRest } from 'utils';
import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators'; import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators';
const SecuritySettings: FC = () => { const SecuritySettings = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();

View File

@@ -29,10 +29,8 @@ import { validate } from 'validators';
interface UserFormProps { interface UserFormProps {
creating: boolean; creating: boolean;
validator: Schema; validator: Schema;
user?: UserType; user?: UserType;
setUser: React.Dispatch<React.SetStateAction<UserType | undefined>>; setUser: React.Dispatch<React.SetStateAction<UserType | undefined>>;
onDoneEditing: () => void; onDoneEditing: () => void;
onCancelEditing: () => void; onCancelEditing: () => void;
} }

View File

@@ -1,12 +1,8 @@
import type { FC } from 'react';
import ComputerIcon from '@mui/icons-material/Computer'; import ComputerIcon from '@mui/icons-material/Computer';
import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import RefreshIcon from '@mui/icons-material/Refresh';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import { import {
Avatar, Avatar,
Button,
Divider, Divider,
List, List,
ListItem, ListItem,
@@ -18,8 +14,8 @@ import type { Theme } from '@mui/material';
import * as APApi from 'api/ap'; import * as APApi from 'api/ap';
import { useRequest } from 'alova'; import { useAutoRequest } from 'alova/client';
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components'; import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import type { APStatusType } from 'types'; import type { APStatusType } from 'types';
import { APNetworkStatus } from 'types'; import { APNetworkStatus } from 'types';
@@ -37,8 +33,12 @@ export const apStatusHighlight = ({ status }: APStatusType, theme: Theme) => {
} }
}; };
const APStatus: FC = () => { const APStatus = () => {
const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus); const {
data: data,
send: loadData,
error
} = useAutoRequest(APApi.readAPStatus, { pollingTime: 5000 });
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle(LL.STATUS_OF(LL.ACCESS_POINT(0))); useLayoutTitle(LL.STATUS_OF(LL.ACCESS_POINT(0)));
@@ -64,7 +64,6 @@ const APStatus: FC = () => {
} }
return ( return (
<>
<List> <List>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
@@ -79,10 +78,7 @@ const APStatus: FC = () => {
<ListItemAvatar> <ListItemAvatar>
<Avatar>IP</Avatar> <Avatar>IP</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText primary={LL.ADDRESS_OF('IP')} secondary={data.ip_address} />
primary={LL.ADDRESS_OF('IP')}
secondary={data.ip_address}
/>
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -107,17 +103,6 @@ const APStatus: FC = () => {
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
</List> </List>
<ButtonRow>
<Button
startIcon={<RefreshIcon />}
variant="outlined"
color="secondary"
onClick={loadData}
>
{LL.REFRESH()}
</Button>
</ButtonRow>
</>
); );
}; };

View File

@@ -1,9 +1,3 @@
import { useEffect } from 'react';
import type { FC } from 'react';
import RefreshIcon from '@mui/icons-material/Refresh';
import { Button } from '@mui/material';
import { import {
Body, Body,
Cell, Cell,
@@ -14,16 +8,20 @@ import {
Table Table
} from '@table-library/react-table-library/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 { useRequest } from 'alova'; import { useAutoRequest } from 'alova/client';
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components'; import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import type { Translation } from 'i18n/i18n-types'; import type { Translation } from 'i18n/i18n-types';
import * as EMSESP from '../main/api'; import { readActivity } from '../../api/app';
import type { Stat } from '../main/types'; import type { Stat } from '../main/types';
const SystemActivity: FC = () => { const SystemActivity = () => {
const { data: data, send: loadData, error } = useRequest(EMSESP.readActivity); const {
data: data,
send: loadData,
error
} = useAutoRequest(readActivity, { pollingTime: 2000 });
const { LL } = useI18nContext(); const { LL } = useI18nContext();
@@ -67,13 +65,6 @@ const SystemActivity: FC = () => {
` `
}); });
useEffect(() => {
const timer = setInterval(() => loadData(), 30000);
return () => {
clearInterval(timer);
};
});
const showName = (id: number) => { const showName = (id: number) => {
const name: keyof Translation['STATUS_NAMES'] = id; const name: keyof Translation['STATUS_NAMES'] = id;
return LL.STATUS_NAMES[name](); return LL.STATUS_NAMES[name]();
@@ -99,7 +90,6 @@ const SystemActivity: FC = () => {
} }
return ( return (
<>
<Table <Table
data={{ nodes: data.stats }} data={{ nodes: data.stats }}
theme={stats_theme} theme={stats_theme}
@@ -128,17 +118,6 @@ const SystemActivity: FC = () => {
</> </>
)} )}
</Table> </Table>
<ButtonRow mt={1}>
<Button
startIcon={<RefreshIcon />}
variant="outlined"
color="secondary"
onClick={loadData}
>
{LL.REFRESH()}
</Button>
</ButtonRow>
</>
); );
}; };

View File

@@ -1,18 +1,13 @@
import type { FC } from 'react';
import AppsIcon from '@mui/icons-material/Apps'; import AppsIcon from '@mui/icons-material/Apps';
import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard'; import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard';
import DevicesIcon from '@mui/icons-material/Devices'; import DevicesIcon from '@mui/icons-material/Devices';
import FolderIcon from '@mui/icons-material/Folder'; import FolderIcon from '@mui/icons-material/Folder';
import MemoryIcon from '@mui/icons-material/Memory'; import MemoryIcon from '@mui/icons-material/Memory';
import RefreshIcon from '@mui/icons-material/Refresh';
import SdCardAlertIcon from '@mui/icons-material/SdCardAlert'; import SdCardAlertIcon from '@mui/icons-material/SdCardAlert';
import SdStorageIcon from '@mui/icons-material/SdStorage'; import SdStorageIcon from '@mui/icons-material/SdStorage';
import TapAndPlayIcon from '@mui/icons-material/TapAndPlay'; import TapAndPlayIcon from '@mui/icons-material/TapAndPlay';
import { import {
Avatar, Avatar,
Box,
Button,
Divider, Divider,
List, List,
ListItem, ListItem,
@@ -22,8 +17,8 @@ import {
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { useRequest } from 'alova'; import { useAutoRequest } from 'alova/client';
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components'; import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import BBQKeesIcon from './bbqkees.svg'; import BBQKeesIcon from './bbqkees.svg';
@@ -32,7 +27,7 @@ function formatNumber(num: number) {
return new Intl.NumberFormat().format(num); return new Intl.NumberFormat().format(num);
} }
const HardwareStatus: FC = () => { const HardwareStatus = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle(LL.STATUS_OF(LL.HARDWARE())); useLayoutTitle(LL.STATUS_OF(LL.HARDWARE()));
@@ -41,7 +36,7 @@ const HardwareStatus: FC = () => {
data: data, data: data,
send: loadData, send: loadData,
error error
} = useRequest(SystemApi.readHardwareStatus, { force: true }); } = useAutoRequest(SystemApi.readHardwareStatus, { pollingTime: 2000 });
const content = () => { const content = () => {
if (!data) { if (!data) {
@@ -49,7 +44,6 @@ const HardwareStatus: FC = () => {
} }
return ( return (
<>
<List> <List>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
@@ -199,21 +193,6 @@ const HardwareStatus: FC = () => {
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
</List> </List>
<Box display="flex" flexWrap="wrap">
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
<ButtonRow>
<Button
startIcon={<RefreshIcon />}
variant="outlined"
color="secondary"
onClick={loadData}
>
{LL.REFRESH()}
</Button>
</ButtonRow>
</Box>
</Box>
</>
); );
}; };

View File

@@ -1,13 +1,9 @@
import type { FC } from 'react';
import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion'; import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion';
import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import RefreshIcon from '@mui/icons-material/Refresh';
import 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 { import {
Avatar, Avatar,
Button,
Divider, Divider,
List, List,
ListItem, ListItem,
@@ -19,8 +15,8 @@ import type { Theme } from '@mui/material';
import * as MqttApi from 'api/mqtt'; import * as MqttApi from 'api/mqtt';
import { useRequest } from 'alova'; import { useAutoRequest } from 'alova/client';
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components'; import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import type { MqttStatusType } from 'types'; import type { MqttStatusType } from 'types';
import { MqttDisconnectReason } from 'types'; import { MqttDisconnectReason } from 'types';
@@ -57,8 +53,12 @@ export const mqttQueueHighlight = (
return theme.palette.warning.main; return theme.palette.warning.main;
}; };
const MqttStatus: FC = () => { const MqttStatus = () => {
const { data: data, send: loadData, error } = useRequest(MqttApi.readMqttStatus); const {
data: data,
send: loadData,
error
} = useAutoRequest(MqttApi.readMqttStatus, { pollingTime: 5000 });
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle(LL.STATUS_OF('MQTT')); useLayoutTitle(LL.STATUS_OF('MQTT'));
@@ -148,7 +148,6 @@ const MqttStatus: FC = () => {
); );
return ( return (
<>
<List> <List>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
@@ -161,17 +160,6 @@ const MqttStatus: FC = () => {
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
{data.enabled && renderConnectionStatus()} {data.enabled && renderConnectionStatus()}
</List> </List>
<ButtonRow>
<Button
startIcon={<RefreshIcon />}
variant="outlined"
color="secondary"
onClick={loadData}
>
{LL.REFRESH()}
</Button>
</ButtonRow>
</>
); );
}; };

View File

@@ -1,11 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import type { FC } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import AccessTimeIcon from '@mui/icons-material/AccessTime'; import AccessTimeIcon from '@mui/icons-material/AccessTime';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import DnsIcon from '@mui/icons-material/Dns'; import DnsIcon from '@mui/icons-material/Dns';
import RefreshIcon from '@mui/icons-material/Refresh';
import SwapVerticalCircleIcon from '@mui/icons-material/SwapVerticalCircle'; import SwapVerticalCircleIcon from '@mui/icons-material/SwapVerticalCircle';
import UpdateIcon from '@mui/icons-material/Update'; import UpdateIcon from '@mui/icons-material/Update';
import { import {
@@ -30,15 +28,19 @@ import type { Theme } from '@mui/material';
import * as NTPApi from 'api/ntp'; import * as NTPApi from 'api/ntp';
import { dialogStyle } from 'CustomTheme'; import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova'; import { useAutoRequest, useRequest } from 'alova/client';
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components'; import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import type { NTPStatusType, Time } from 'types'; import type { NTPStatusType, Time } from 'types';
import { NTPSyncStatus } from 'types'; import { NTPSyncStatus } from 'types';
import { formatDateTime, formatLocalDateTime } from 'utils'; import { formatDateTime, formatLocalDateTime } from 'utils';
const NTPStatus: FC = () => { const NTPStatus = () => {
const { data: data, send: loadData, error } = useRequest(NTPApi.readNTPStatus); const {
data: data,
send: loadData,
error
} = useAutoRequest(NTPApi.readNTPStatus, { pollingTime: 5000 });
const [localTime, setLocalTime] = useState<string>(''); const [localTime, setLocalTime] = useState<string>('');
const [settingTime, setSettingTime] = useState<boolean>(false); const [settingTime, setSettingTime] = useState<boolean>(false);
@@ -215,18 +217,6 @@ const NTPStatus: FC = () => {
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
</List> </List>
<Box display="flex" flexWrap="wrap"> <Box display="flex" flexWrap="wrap">
<Box flexGrow={1}>
<ButtonRow>
<Button
startIcon={<RefreshIcon />}
variant="outlined"
color="secondary"
onClick={loadData}
>
{LL.REFRESH()}
</Button>
</ButtonRow>
</Box>
{data && !isNtpActive(data) && ( {data && !isNtpActive(data) && (
<Box flexWrap="nowrap" whiteSpace="nowrap"> <Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow> <ButtonRow>

View File

@@ -1,16 +1,12 @@
import type { FC } from 'react';
import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import DnsIcon from '@mui/icons-material/Dns'; import DnsIcon from '@mui/icons-material/Dns';
import GiteIcon from '@mui/icons-material/Gite'; import GiteIcon from '@mui/icons-material/Gite';
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 SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import SettingsInputComponentIcon from '@mui/icons-material/SettingsInputComponent'; import SettingsInputComponentIcon from '@mui/icons-material/SettingsInputComponent';
import WifiIcon from '@mui/icons-material/Wifi'; import WifiIcon from '@mui/icons-material/Wifi';
import { import {
Avatar, Avatar,
Button,
Divider, Divider,
List, List,
ListItem, ListItem,
@@ -22,8 +18,8 @@ import type { Theme } from '@mui/material';
import * as NetworkApi from 'api/network'; import * as NetworkApi from 'api/network';
import { useRequest } from 'alova'; import { useAutoRequest } from 'alova/client';
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components'; import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import type { NetworkStatusType } from 'types'; import type { NetworkStatusType } from 'types';
import { NetworkConnectionStatus } from 'types'; import { NetworkConnectionStatus } from 'types';
@@ -84,12 +80,12 @@ const IPs = (status: NetworkStatusType) => {
return status.local_ip + ', ' + status.local_ipv6; return status.local_ip + ', ' + status.local_ipv6;
}; };
const NetworkStatus: FC = () => { const NetworkStatus = () => {
const { const {
data: data, data: data,
send: loadData, send: loadData,
error error
} = useRequest(NetworkApi.readNetworkStatus); } = useAutoRequest(NetworkApi.readNetworkStatus, { pollingTime: 5000 });
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle(LL.STATUS_OF(LL.NETWORK(1))); useLayoutTitle(LL.STATUS_OF(LL.NETWORK(1)));
@@ -125,7 +121,6 @@ const NetworkStatus: FC = () => {
} }
return ( return (
<>
<List> <List>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
@@ -216,21 +211,9 @@ const NetworkStatus: FC = () => {
secondary={dnsServers(data)} secondary={dnsServers(data)}
/> />
</ListItem> </ListItem>
<Divider variant="inset" component="li" />
</> </>
)} )}
</List> </List>
<ButtonRow>
<Button
startIcon={<RefreshIcon />}
variant="outlined"
color="secondary"
onClick={loadData}
>
{LL.REFRESH()}
</Button>
</ButtonRow>
</>
); );
}; };

View File

@@ -1,20 +1,19 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import type { FC } from 'react';
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { useRequest } from 'alova'; import { useRequest } from 'alova/client';
import { FormLoader } from 'components'; import { FormLoader } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
const RESTART_TIMEOUT = 2 * 60 * 1000; const RESTART_TIMEOUT = 2 * 60 * 1000;
const POLL_INTERVAL = 3000; const POLL_INTERVAL = 3000;
const RestartMonitor: FC = () => { const RestartMonitor = () => {
const [failed, setFailed] = useState<boolean>(false); const [failed, setFailed] = useState<boolean>(false);
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>(); const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const { send } = useRequest(SystemApi.readSystemStatus, { force: true }); const { send } = useRequest(SystemApi.readSystemStatus);
const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT); const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT);
const poll = useRef(async () => { const poll = useRef(async () => {

View File

@@ -1,4 +1,4 @@
import { type FC, useContext, useState } from 'react'; import { useContext, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
@@ -10,7 +10,6 @@ import DirectionsBusIcon from '@mui/icons-material/DirectionsBus';
import LogoDevIcon from '@mui/icons-material/LogoDev'; import LogoDevIcon from '@mui/icons-material/LogoDev';
import MemoryIcon from '@mui/icons-material/Memory'; import MemoryIcon from '@mui/icons-material/Memory';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
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 SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import TimerIcon from '@mui/icons-material/Timer'; import TimerIcon from '@mui/icons-material/Timer';
@@ -18,7 +17,6 @@ import UpgradeIcon from '@mui/icons-material/Upgrade';
import WifiIcon from '@mui/icons-material/Wifi'; import WifiIcon from '@mui/icons-material/Wifi';
import { import {
Avatar, Avatar,
Box,
Button, Button,
Dialog, Dialog,
DialogActions, DialogActions,
@@ -34,7 +32,7 @@ import {
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { dialogStyle } from 'CustomTheme'; import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova'; import { useAutoRequest, useRequest } from 'alova/client';
import { busConnectionStatus } from 'app/main/types'; import { busConnectionStatus } from 'app/main/types';
import { FormLoader, SectionContent, useLayoutTitle } from 'components'; import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import ListMenuItem from 'components/layout/ListMenuItem'; import ListMenuItem from 'components/layout/ListMenuItem';
@@ -44,7 +42,7 @@ import { NTPSyncStatus, NetworkConnectionStatus } from 'types';
import RestartMonitor from './RestartMonitor'; import RestartMonitor from './RestartMonitor';
const SystemStatus: FC = () => { const SystemStatus = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -69,7 +67,10 @@ const SystemStatus: FC = () => {
data: data, data: data,
send: loadData, send: loadData,
error error
} = useRequest(SystemApi.readSystemStatus, { force: true }); } = useAutoRequest(SystemApi.readSystemStatus, {
initialData: [],
pollingTime: 5000
});
const theme = useTheme(); const theme = useTheme();
@@ -390,17 +391,6 @@ const SystemStatus: FC = () => {
</List> </List>
{renderRestartDialog()} {renderRestartDialog()}
<Box mt={2} display="flex" flexWrap="wrap">
<Button
startIcon={<RefreshIcon />}
variant="outlined"
color="secondary"
onClick={loadData}
>
{LL.REFRESH()}
</Button>
</Box>
</> </>
); );
}; };

View File

@@ -1,5 +1,4 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import type { FC } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import DownloadIcon from '@mui/icons-material/GetApp'; import DownloadIcon from '@mui/icons-material/GetApp';
@@ -17,8 +16,7 @@ import {
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { fetchLogES } from 'api/system'; import { fetchLogES } from 'api/system';
import { useSSE } from '@alova/scene-react'; import { useRequest, useSSE } from 'alova/client';
import { useRequest } from 'alova';
import { import {
BlockFormControlLabel, BlockFormControlLabel,
BlockNavigation, BlockNavigation,
@@ -72,7 +70,7 @@ const levelLabel = (level: LogLevel) => {
} }
}; };
const SystemLog: FC = () => { const SystemLog = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle(LL.LOG_OF(LL.SYSTEM(0))); useLayoutTitle(LL.LOG_OF(LL.SYSTEM(0)));

View File

@@ -1,4 +1,4 @@
import { type ChangeEventHandler, type FC, useContext } from 'react'; import { type ChangeEventHandler, useContext } from 'react';
import { MenuItem, TextField } from '@mui/material'; import { MenuItem, TextField } from '@mui/material';
@@ -16,7 +16,7 @@ import { I18nContext } from 'i18n/i18n-react';
import type { Locales } from 'i18n/i18n-types'; import type { Locales } from 'i18n/i18n-types';
import { loadLocaleAsync } from 'i18n/i18n-util.async'; import { loadLocaleAsync } from 'i18n/i18n-util.async';
const LanguageSelector: FC = () => { const LanguageSelector = () => {
const { setLocale, locale } = useContext(I18nContext); const { setLocale, locale } = useContext(I18nContext);
const onLocaleSelected: ChangeEventHandler<HTMLInputElement> = async ({ const onLocaleSelected: ChangeEventHandler<HTMLInputElement> = async ({

View File

@@ -4,8 +4,7 @@ import { useLocation } from 'react-router-dom';
import { Box, Toolbar } from '@mui/material'; import { Box, Toolbar } from '@mui/material';
import { PROJECT_NAME } from 'api/env'; import { PROJECT_NAME } from 'env';
import type { RequiredChildrenProps } from 'utils'; import type { RequiredChildrenProps } from 'utils';
import LayoutAppBar from './LayoutAppBar'; import LayoutAppBar from './LayoutAppBar';

View File

@@ -1,4 +1,3 @@
import type { FC } from 'react';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowBackIcon from '@mui/icons-material/ArrowBack';
@@ -12,7 +11,7 @@ interface LayoutAppBarProps {
onToggleDrawer: () => void; onToggleDrawer: () => void;
} }
const LayoutAppBar: FC<LayoutAppBarProps> = ({ title, onToggleDrawer }) => { const LayoutAppBar = ({ title, onToggleDrawer }: LayoutAppBarProps) => {
const pathnames = useLocation() const pathnames = useLocation()
.pathname.split('/') .pathname.split('/')
.filter((x) => x); .filter((x) => x);

View File

@@ -1,8 +1,6 @@
import type { FC } from 'react';
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material'; import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
import { PROJECT_NAME } from 'api/env'; import { PROJECT_NAME } from 'env';
import { DRAWER_WIDTH } from './Layout'; import { DRAWER_WIDTH } from './Layout';
import LayoutMenu from './LayoutMenu'; import LayoutMenu from './LayoutMenu';
@@ -23,7 +21,7 @@ interface LayoutDrawerProps {
onClose: () => void; onClose: () => void;
} }
const LayoutDrawer: FC<LayoutDrawerProps> = ({ mobileOpen, onClose }) => { const LayoutDrawerProps = ({ mobileOpen, onClose }: LayoutDrawerProps) => {
const drawer = ( const drawer = (
<> <>
<Toolbar disableGutters> <Toolbar disableGutters>
@@ -68,4 +66,4 @@ const LayoutDrawer: FC<LayoutDrawerProps> = ({ mobileOpen, onClose }) => {
); );
}; };
export default LayoutDrawer; export default LayoutDrawerProps;

View File

@@ -1,5 +1,4 @@
import { useContext, useState } from 'react'; import { useContext, useState } from 'react';
import type { FC } from 'react';
import AccountCircleIcon from '@mui/icons-material/AccountCircle'; import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import AssessmentIcon from '@mui/icons-material/Assessment'; import AssessmentIcon from '@mui/icons-material/Assessment';
@@ -30,7 +29,7 @@ import LayoutMenuItem from 'components/layout/LayoutMenuItem';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
const LayoutMenu: FC = () => { const LayoutMenu = () => {
const { me, signOut } = useContext(AuthenticatedContext); const { me, signOut } = useContext(AuthenticatedContext);
const { LL } = useI18nContext(); const { LL } = useI18nContext();

View File

@@ -1,4 +1,3 @@
import type { FC } from 'react';
import { Link, useLocation } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
@@ -13,12 +12,12 @@ interface LayoutMenuItemProps {
disabled?: boolean; disabled?: boolean;
} }
const LayoutMenuItem: FC<LayoutMenuItemProps> = ({ const LayoutMenuItem = ({
icon: Icon, icon: Icon,
label, label,
to, to,
disabled disabled
}) => { }: LayoutMenuItemProps) => {
const { pathname } = useLocation(); const { pathname } = useLocation();
const selected = routeMatches(to, pathname); const selected = routeMatches(to, pathname);

View File

@@ -1,4 +1,3 @@
import type { FC } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import NavigateNextIcon from '@mui/icons-material/NavigateNext'; import NavigateNextIcon from '@mui/icons-material/NavigateNext';
@@ -34,14 +33,14 @@ function RenderIcon({ icon: Icon, bgcolor, label, text }: ListMenuItemProps) {
); );
} }
const LayoutMenuItem: FC<ListMenuItemProps> = ({ const LayoutMenuItem = ({
icon, icon,
bgcolor, bgcolor,
label, label,
text, text,
to, to,
disabled disabled
}) => ( }: ListMenuItemProps) => (
<> <>
{to && !disabled ? ( {to && !disabled ? (
<ListItem <ListItem

View File

@@ -1,49 +0,0 @@
import type { FC } from 'react';
import WarningIcon from '@mui/icons-material/Warning';
import { Box, Paper, Typography } from '@mui/material';
interface ApplicationErrorProps {
message?: string;
}
const ApplicationError: FC<ApplicationErrorProps> = ({ message }) => (
<Box display="flex" height="100vh" justifyContent="center" flexDirection="column">
<Paper
elevation={10}
sx={{
textAlign: 'center',
padding: '280px 0 40px 0',
backgroundImage: 'url("/app/icon.png")',
backgroundRepeat: 'no-repeat',
backgroundPosition: '50% 40px',
backgroundSize: '200px auto',
width: '100%',
borderRadius: 0
}}
>
<Box
display="flex"
flexDirection="row"
justifyContent="center"
alignItems="center"
mb={2}
>
<WarningIcon fontSize="large" color="error" />
<Box ml={2}>
<Typography variant="h4">Application Error</Typography>
</Box>
</Box>
<Typography variant="subtitle1" gutterBottom>
Failed to configure the application, please refresh to try again.
</Typography>
{message && (
<Typography variant="subtitle2" gutterBottom>
{message}
</Typography>
)}
</Paper>
</Box>
);
export default ApplicationError;

View File

@@ -1,5 +1,3 @@
import type { FC } from 'react';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import { Box, Button, CircularProgress, Typography } from '@mui/material'; import { Box, Button, CircularProgress, Typography } from '@mui/material';
@@ -12,11 +10,11 @@ interface FormLoaderProps {
onRetry?: () => void; onRetry?: () => void;
} }
const FormLoader: FC<FormLoaderProps> = ({ const FormLoader = ({
errorMessage, errorMessage,
onRetry, onRetry,
message = 'Loading…' message = 'Loading…'
}) => { }: FormLoaderProps) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
if (errorMessage) { if (errorMessage) {

View File

@@ -1,5 +1,3 @@
import type { FC } from 'react';
import { Box, CircularProgress, Typography } from '@mui/material'; import { Box, CircularProgress, Typography } from '@mui/material';
import type { Theme } from '@mui/material'; import type { Theme } from '@mui/material';
@@ -9,7 +7,7 @@ interface LoadingSpinnerProps {
height?: number | string; height?: number | string;
} }
const LoadingSpinner: FC<LoadingSpinnerProps> = ({ height = '100%' }) => { const LoadingSpinner = ({ height = '100%' }: LoadingSpinnerProps) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
return ( return (

View File

@@ -1,3 +1,2 @@
export { default as ApplicationError } from './ApplicationError';
export { default as LoadingSpinner } from './LoadingSpinner'; export { default as LoadingSpinner } from './LoadingSpinner';
export { default as FormLoader } from './FormLoader'; export { default as FormLoader } from './FormLoader';

View File

@@ -1,4 +1,3 @@
import type { FC } from 'react';
import type { Blocker } from 'react-router-dom'; import type { Blocker } from 'react-router-dom';
import { import {
@@ -12,11 +11,7 @@ import {
import { dialogStyle } from 'CustomTheme'; import { dialogStyle } from 'CustomTheme';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
interface BlockNavigationProps { const BlockNavigation = ({ blocker }: { blocker: Blocker }) => {
blocker: Blocker;
}
const BlockNavigation: FC<BlockNavigationProps> = ({ blocker }) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
return ( return (

View File

@@ -2,8 +2,7 @@ import { useContext, useEffect } from 'react';
import type { FC } from 'react'; import type { FC } from 'react';
import { Navigate, useLocation } from 'react-router-dom'; import { Navigate, useLocation } from 'react-router-dom';
import { storeLoginRedirect } from 'api/authentication'; import { storeLoginRedirect } from 'components/routing/authentication';
import type { AuthenticatedContextValue } from 'contexts/authentication/context'; import type { AuthenticatedContextValue } from 'contexts/authentication/context';
import { import {
AuthenticatedContext, AuthenticatedContext,

View File

@@ -2,8 +2,7 @@ import { useContext } from 'react';
import type { FC } from 'react'; import type { FC } from 'react';
import { Navigate } from 'react-router-dom'; import { Navigate } from 'react-router-dom';
import * as AuthenticationApi from 'api/authentication'; import { fetchLoginRedirect } from 'components/routing/authentication';
import { AuthenticationContext } from 'contexts/authentication'; import { AuthenticationContext } from 'contexts/authentication';
import type { RequiredChildrenProps } from 'utils'; import type { RequiredChildrenProps } from 'utils';
@@ -11,7 +10,7 @@ const RequireUnauthenticated: FC<RequiredChildrenProps> = ({ children }) => {
const authenticationContext = useContext(AuthenticationContext); const authenticationContext = useContext(AuthenticationContext);
return authenticationContext.me ? ( return authenticationContext.me ? (
<Navigate to={AuthenticationApi.fetchLoginRedirect()} /> <Navigate to={fetchLoginRedirect()} />
) : ( ) : (
<>{children}</> <>{children}</>
); );

View File

@@ -4,7 +4,7 @@ import type * as H from 'history';
import { jwtDecode } from 'jwt-decode'; import { jwtDecode } from 'jwt-decode';
import type { Me, SignInRequest, SignInResponse } from 'types'; import type { Me, SignInRequest, SignInResponse } from 'types';
import { ACCESS_TOKEN, alovaInstance } from './endpoints'; import { ACCESS_TOKEN, alovaInstance } from '../../api/endpoints';
export const SIGN_IN_PATHNAME = 'loginPathname'; export const SIGN_IN_PATHNAME = 'loginPathname';
export const SIGN_IN_SEARCH = 'loginSearch'; export const SIGN_IN_SEARCH = 'loginSearch';

View File

@@ -0,0 +1,112 @@
// Code inspired by https://medium.com/@dprincecoder/creating-a-drag-and-drop-file-upload-component-in-react-a-step-by-step-guide-4d93b6cc21e0
// (c) Prince Azubuike
import { type ChangeEvent, useRef, useState } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
import UploadIcon from '@mui/icons-material/Upload';
import { Box, Button } from '@mui/material';
import { useI18nContext } from 'i18n/i18n-react';
import './drag-drop.css';
const DragNdrop = ({ onFileSelected }) => {
const [file, setFile] = useState<File>();
const inputRef = useRef<HTMLInputElement | null>(null);
const { LL } = useI18nContext();
const checkFileExtension = (file: File) => {
const validExtensions = ['.json', '.txt', '.csv', '.bin', '.md5'];
const fileName = file.name;
const fileExtension = fileName.substring(fileName.lastIndexOf('.'));
if (validExtensions.includes(fileExtension)) {
setFile(file);
} else {
alert('Invalid file type');
}
};
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) {
return;
}
checkFileExtension(e.target.files[0]);
e.target.value = ''; // this is to allow the same file to be selected again
};
const handleDrop = (event) => {
event.preventDefault();
const droppedFiles = event.dataTransfer.files;
if (droppedFiles.length > 0) {
checkFileExtension(droppedFiles[0]);
}
};
const handleRemoveFile = (event) => {
event.stopPropagation();
setFile(undefined);
};
const handleUploadClick = (event) => {
event.stopPropagation();
onFileSelected(file);
};
const handleBrowseClick = () => {
inputRef.current?.click();
};
return (
<div
className={`document-uploader ${file ? 'upload-box active' : 'upload-box'}`}
onDrop={handleDrop}
onDragOver={(event) => event.preventDefault()}
onClick={handleBrowseClick}
>
<div className="upload-info">
<CloudUploadIcon sx={{ marginRight: 4 }} color="primary" fontSize="large" />
<p>{LL.UPLOAD_DRAG()}</p>
</div>
<input
type="file"
hidden
onChange={handleFileChange}
ref={inputRef}
accept=".json,.txt,.csv,.bin,.md5"
multiple={false}
style={{ display: 'none' }}
/>
{file && (
<>
<div className="file-info">
<p>{file.name}</p>
</div>
<Box>
<Button
startIcon={<CancelIcon />}
variant="outlined"
color="error"
onClick={(e) => handleRemoveFile(e)}
>
{LL.CANCEL()}
</Button>
<Button
sx={{ ml: 2 }}
startIcon={<UploadIcon />}
variant="outlined"
color="primary"
onClick={handleUploadClick}
>
{LL.UPLOAD()}
</Button>
</Box>
</>
)}
</div>
);
};
export default DragNdrop;

View File

@@ -1,94 +1,82 @@
import { Fragment } from 'react'; import { useEffect, useState } from 'react';
import type { FC } from 'react'; import { toast } from 'react-toastify';
import { useDropzone } from 'react-dropzone';
import type { DropzoneState } from 'react-dropzone';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import CloudUploadIcon from '@mui/icons-material/CloudUpload'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import { Box, Button, LinearProgress, Typography, useTheme } from '@mui/material'; import { Box, Button, LinearProgress, Typography } from '@mui/material';
import type { Theme } from '@mui/material';
import type { Progress } from 'alova'; import * as SystemApi from 'api/system';
import { useRequest } from 'alova/client';
import RestartMonitor from 'app/status/RestartMonitor';
import MessageBox from 'components/MessageBox';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
const getBorderColor = (theme: Theme, props: DropzoneState) => { import DragNdrop from './DragNdrop';
if (props.isDragAccept) {
return theme.palette.success.main;
}
if (props.isDragReject) {
return theme.palette.error.main;
}
if (props.isDragActive) {
return theme.palette.info.main;
}
return theme.palette.grey[700];
};
export interface SingleUploadProps { const SingleUpload = () => {
onDrop: (acceptedFiles: File[]) => void; const [md5, setMd5] = useState<string>();
onCancel: () => void; const [restarting, setRestarting] = useState<boolean>(false);
isUploading: boolean; const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
progress: Progress;
}
const SingleUpload: FC<SingleUploadProps> = ({ const [file, setFile] = useState<File>();
onDrop,
onCancel,
isUploading,
progress
}) => {
const uploading = isUploading && progress.total > 0;
const dropzoneState = useDropzone({
onDrop,
accept: {
'application/octet-stream': ['.bin'],
'application/json': ['.json'],
'text/plain': ['.md5']
},
disabled: isUploading,
multiple: false
});
const { getRootProps, getInputProps } = dropzoneState;
const theme = useTheme();
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const progressText = () => { const {
if (uploading) { loading: isUploading,
return ( uploading: progress,
LL.UPLOADING() + send: sendUpload,
': ' + abort: cancelUpload
Math.round((progress.loaded * 100) / progress.total) + } = useRequest(SystemApi.uploadFile, {
'%' immediate: false
); }).onSuccess(({ data }) => {
if (data) {
setMd5(data.md5 as string);
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
setFile(undefined);
} else {
setRestartNeeded(true);
} }
return LL.UPLOAD_DROP_TEXT(); });
const { send: restartCommand } = useRequest(SystemApi.restart(), {
immediate: false
});
const restart = async () => {
await restartCommand().catch((error: Error) => {
toast.error(error.message);
});
setRestarting(true);
}; };
return ( useEffect(async () => {
<Box if (file) {
{...getRootProps({ console.log('going to upload file ', file.name);
sx: { await sendUpload(file).catch((error: Error) => {
py: 4, if (error.message === 'The user aborted a request') {
px: 2, toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
borderWidth: 2, } else if (error.message === 'Network Error') {
borderRadius: 2, toast.warning('Invalid file extension or incompatible bin file');
borderStyle: 'dashed', } else {
color: theme.palette.grey[400], toast.error(error.message);
transition: 'border .24s ease-in-out',
width: '100%',
cursor: uploading ? 'default' : 'pointer',
borderColor: getBorderColor(theme, dropzoneState)
} }
})} });
> }
<input {...getInputProps()} /> }, [file]);
<Box flexDirection="column" display="flex" alignItems="center">
<CloudUploadIcon fontSize="large" /> return (
<Typography variant="h6">{progressText()}</Typography> <>
{uploading && ( <Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
<Fragment> {LL.UPLOAD()}
</Typography>
<Box mb={2} color="warning.main">
<Typography variant="body2">{LL.UPLOAD_TEXT()}</Typography>
</Box>
{isUploading || restartNeeded ? (
<>
<Box width="100%" p={2}> <Box width="100%" p={2}>
<LinearProgress <LinearProgress
variant="determinate" variant="determinate"
@@ -101,18 +89,44 @@ const SingleUpload: FC<SingleUploadProps> = ({
} }
/> />
</Box> </Box>
{!restartNeeded && (
<Button <Button
sx={{ ml: 2 }}
startIcon={<CancelIcon />} startIcon={<CancelIcon />}
variant="outlined" variant="outlined"
color="secondary" color="error"
onClick={onCancel} onClick={cancelUpload}
> >
{LL.CANCEL()} {LL.CANCEL()}
</Button> </Button>
</Fragment>
)} )}
</>
) : (
<DragNdrop onFileSelected={setFile} />
)}
{md5 && (
<Box mt={2}>
<Typography variant="body2">{'MD5: ' + md5}</Typography>
</Box> </Box>
</Box> )}
{restartNeeded && (
<MessageBox mt={2} level="warning" message={LL.RESTART_TEXT(0)}>
<Button
startIcon={<PowerSettingsNewIcon />}
variant="contained"
color="error"
onClick={restart}
>
{LL.RESTART()}
</Button>
</MessageBox>
)}
{restarting && <RestartMonitor />}
</>
); );
}; };

View File

@@ -0,0 +1,33 @@
.document-uploader {
border: 2px dashed #4282fe;
background-color: #2e3339;
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
border-radius: 8px;
cursor: pointer;
&.active {
border-color: #6dc24b;
}
.upload-info {
display: flex;
align-items: center;
}
.file-info {
display: flex;
flex-direction: column;
width: 100%;
justify-content: space-between;
align-items: center;
p {
font-size: 14px;
color: #6dc24b;
}
}
}

View File

@@ -3,11 +3,12 @@ import type { FC } from 'react';
import { redirect } from 'react-router-dom'; import { redirect } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import * as AuthenticationApi from 'api/authentication';
import { ACCESS_TOKEN } from 'api/endpoints'; import { ACCESS_TOKEN } from 'api/endpoints';
import { useRequest } from 'alova'; import * as AuthenticationApi from 'components/routing/authentication';
import { useRequest } from 'alova/client';
import { LoadingSpinner } from 'components'; import { LoadingSpinner } from 'components';
import { verifyAuthorization } from 'components/routing/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import type { Me } from 'types'; import type { Me } from 'types';
import type { RequiredChildrenProps } from 'utils'; import type { RequiredChildrenProps } from 'utils';
@@ -20,12 +21,9 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
const [initialized, setInitialized] = useState<boolean>(false); const [initialized, setInitialized] = useState<boolean>(false);
const [me, setMe] = useState<Me>(); const [me, setMe] = useState<Me>();
const { send: verifyAuthorization } = useRequest( const { send: sendVerifyAuthorization } = useRequest(verifyAuthorization(), {
AuthenticationApi.verifyAuthorization(),
{
immediate: false immediate: false
} });
);
const signIn = (accessToken: string) => { const signIn = (accessToken: string) => {
try { try {
@@ -43,7 +41,6 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
AuthenticationApi.clearAccessToken(); AuthenticationApi.clearAccessToken();
setMe(undefined); setMe(undefined);
if (doRedirect) { if (doRedirect) {
// navigate('/');
redirect('/'); redirect('/');
} }
}; };
@@ -51,7 +48,7 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
const refresh = useCallback(async () => { const refresh = useCallback(async () => {
const accessToken = AuthenticationApi.getStorage().getItem(ACCESS_TOKEN); const accessToken = AuthenticationApi.getStorage().getItem(ACCESS_TOKEN);
if (accessToken) { if (accessToken) {
await verifyAuthorization() await sendVerifyAuthorization()
.then(() => { .then(() => {
setMe(AuthenticationApi.decodeMeJWT(accessToken)); setMe(AuthenticationApi.decodeMeJWT(accessToken));
setInitialized(true); setInitialized(true);

View File

@@ -94,8 +94,6 @@ const de: Translation = {
APPLICATION: 'Anwendung', APPLICATION: 'Anwendung',
CUSTOMIZATIONS: 'Anpassungen', CUSTOMIZATIONS: 'Anpassungen',
APPLICATION_RESTARTING: 'EMS-ESP startet neu', APPLICATION_RESTARTING: 'EMS-ESP startet neu',
INTERFACE_BOARD_PROFILE: 'Interface Platinenprofil',
BOARD_PROFILE_TEXT: 'Wählen Sie ein vorkonfiguriertes Platinenprofil aus der Liste unten aus oder wählen Sie "Custom", um Ihre eigenen Hardwareeinstellungen zu konfigurieren',
BOARD_PROFILE: 'Platinenprofil', BOARD_PROFILE: 'Platinenprofil',
CUSTOM: 'Custom', CUSTOM: 'Custom',
GPIO_OF: '{0} GPIO', GPIO_OF: '{0} GPIO',
@@ -103,7 +101,7 @@ const de: Translation = {
TEMPERATURE: 'Temperatur', TEMPERATURE: 'Temperatur',
PHY_TYPE: 'Eth PHY Typ', PHY_TYPE: 'Eth PHY Typ',
DISABLED: 'deaktiviert', DISABLED: 'deaktiviert',
TX_MODE: 'Tx Modus', TX_MODE: 'EMS Tx Modus',
HARDWARE: 'Hardware', HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}', EMS_BUS: '{{BUS|EMS BUS}}',
GENERAL_OPTIONS: 'Allgemeine Optionen', GENERAL_OPTIONS: 'Allgemeine Optionen',
@@ -128,7 +126,7 @@ const de: Translation = {
BOOLEAN_FORMAT_API: 'Boolesches Format API/MQTT', BOOLEAN_FORMAT_API: 'Boolesches Format API/MQTT',
ENUM_FORMAT: 'Enum Format API/MQTT', ENUM_FORMAT: 'Enum Format API/MQTT',
INDEX: 'Index', INDEX: 'Index',
ENABLE_PARASITE: 'Parasitäre Stomversorgung', ENABLE_PARASITE: '1-wire Parasitäre Stomversorgung',
LOGGING: 'Protokollierung', LOGGING: 'Protokollierung',
LOG_HEX: 'EMS-Telegramme hexadezimal protokollieren', LOG_HEX: 'EMS-Telegramme hexadezimal protokollieren',
ENABLE_SYSLOG: 'Syslog aktivieren', ENABLE_SYSLOG: 'Syslog aktivieren',
@@ -169,7 +167,7 @@ const de: Translation = {
SYSTEM: 'System', SYSTEM: 'System',
LOG_OF: '{0}protokoll', LOG_OF: '{0}protokoll',
STATUS_OF: '{0} Status', STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Hoch-/Herunterladen', DOWNLOAD_UPLOAD: 'Herunterladen/Hochladen',
VERSION_ON: 'Sie verwenden derzeit', VERSION_ON: 'Sie verwenden derzeit',
CLOSE: 'Schließen', CLOSE: 'Schließen',
USE: 'Verwenden Sie', USE: 'Verwenden Sie',
@@ -194,7 +192,6 @@ const de: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Herunterladen geplanter Befehle', DOWNLOAD_SCHEDULE_TEXT: 'Herunterladen geplanter Befehle',
DOWNLOAD_SETTINGS_TEXT: 'Herunterladen der Anwendungseinstellungen. Vorsicht beim Teilen der Einstellungen, da sie Passwörter und andere sensitive Einstellungen enthalten', DOWNLOAD_SETTINGS_TEXT: 'Herunterladen der Anwendungseinstellungen. Vorsicht beim Teilen der Einstellungen, da sie Passwörter und andere sensitive Einstellungen enthalten',
UPLOAD_TEXT: 'Hochladen von neuer Firmware (.bin), Geräte- oder Entitätseinstellungen (.json), zur optionalen Validitätsprüfung zuerst die (.md5) Datei hochladen', UPLOAD_TEXT: 'Hochladen von neuer Firmware (.bin), Geräte- oder Entitätseinstellungen (.json), zur optionalen Validitätsprüfung zuerst die (.md5) Datei hochladen',
UPLOADING: 'Hochladen',
UPLOAD_DROP_TEXT: 'Klicken Sie hier, oder ziehen eine Datei hierher', UPLOAD_DROP_TEXT: 'Klicken Sie hier, oder ziehen eine Datei hierher',
ERROR: 'Unerwarteter Fehler, bitter versuchen Sie es erneut', ERROR: 'Unerwarteter Fehler, bitter versuchen Sie es erneut',
TIME_SET: 'Zeit gesetzt', TIME_SET: 'Zeit gesetzt',
@@ -325,7 +322,7 @@ const de: Translation = {
SYSTEM_MEMORY: 'Systemspeicher', SYSTEM_MEMORY: 'Systemspeicher',
APPLICATION_SETTINGS_1: 'Ändern Sie die EMS-ESP-Anwendungseinstellungen', APPLICATION_SETTINGS_1: 'Ändern Sie die EMS-ESP-Anwendungseinstellungen',
SECURITY_1: 'Benutzer hinzufügen oder entfernen', SECURITY_1: 'Benutzer hinzufügen oder entfernen',
UPLOAD_DOWNLOAD_1: 'Einstellungen und Firmware hochladen/herunterladen', DOWNLOAD_UPLOAD_1: 'Einstellungen und Firmware herunterladen/hochladen',
MODULES: 'Module', MODULES: 'Module',
MODULES_1: 'Externe Module aktivieren oder deaktivieren', MODULES_1: 'Externe Module aktivieren oder deaktivieren',
MODULES_UPDATED: 'Module aktualisiert', MODULES_UPDATED: 'Module aktualisiert',
@@ -333,7 +330,10 @@ const de: Translation = {
MODULES_NONE: 'Keine externen Module erkannt', MODULES_NONE: 'Keine externen Module erkannt',
RENAME: 'Umbenennen', RENAME: 'Umbenennen',
ENABLE_MODBUS: 'Modbus aktivieren', ENABLE_MODBUS: 'Modbus aktivieren',
VIEW_LOG: 'Sehen Sie sich das Protokoll an, um Probleme zu diagnostizieren' VIEW_LOG: 'Sehen Sie sich das Protokoll an, um Probleme zu diagnostizieren',
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
SERVICES: 'Services', // TODO translate
ALLVALUES: 'All Values' // TODO translate
}; };
export default de; export default de;

View File

@@ -94,8 +94,6 @@ const en: Translation = {
APPLICATION: 'Application', APPLICATION: 'Application',
CUSTOMIZATIONS: 'Customizations', CUSTOMIZATIONS: 'Customizations',
APPLICATION_RESTARTING: 'EMS-ESP is restarting', APPLICATION_RESTARTING: 'EMS-ESP is restarting',
INTERFACE_BOARD_PROFILE: 'Interface Board Profile',
BOARD_PROFILE_TEXT: 'Select a pre-configured interface board profile from the list below or choose Custom to configure your own hardware settings',
BOARD_PROFILE: 'Board Profile', BOARD_PROFILE: 'Board Profile',
CUSTOM: 'Custom', CUSTOM: 'Custom',
GPIO_OF: '{0} GPIO', GPIO_OF: '{0} GPIO',
@@ -103,7 +101,7 @@ const en: Translation = {
TEMPERATURE: 'Temperature', TEMPERATURE: 'Temperature',
PHY_TYPE: 'Eth PHY Type', PHY_TYPE: 'Eth PHY Type',
DISABLED: 'disabled', DISABLED: 'disabled',
TX_MODE: 'Tx Mode', TX_MODE: 'EMS Tx Mode',
HARDWARE: 'Hardware', HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}', EMS_BUS: '{{BUS|EMS BUS}}',
GENERAL_OPTIONS: 'General Options', GENERAL_OPTIONS: 'General Options',
@@ -124,11 +122,11 @@ const en: Translation = {
TRIGGER_TIME: 'Trigger Time', TRIGGER_TIME: 'Trigger Time',
COLD_SHOT_DURATION: 'Cold Shot Duration', COLD_SHOT_DURATION: 'Cold Shot Duration',
FORMATTING_OPTIONS: 'Formatting Options', FORMATTING_OPTIONS: 'Formatting Options',
BOOLEAN_FORMAT_DASHBOARD: 'Boolean Format Dashboard', BOOLEAN_FORMAT_DASHBOARD: 'Boolean Format Web',
BOOLEAN_FORMAT_API: 'Boolean Format API/MQTT', BOOLEAN_FORMAT_API: 'Boolean Format API/MQTT',
ENUM_FORMAT: 'Enum Format API/MQTT', ENUM_FORMAT: 'Enum Format API/MQTT',
INDEX: 'Index', INDEX: 'Index',
ENABLE_PARASITE: 'Enable parasite power', ENABLE_PARASITE: 'Enable 1-Wire Parasite-Power',
LOGGING: 'Logging', LOGGING: 'Logging',
LOG_HEX: 'Log EMS telegrams in hexadecimal', LOG_HEX: 'Log EMS telegrams in hexadecimal',
ENABLE_SYSLOG: 'Enable Syslog', ENABLE_SYSLOG: 'Enable Syslog',
@@ -169,7 +167,7 @@ const en: Translation = {
SYSTEM: 'System', SYSTEM: 'System',
LOG_OF: '{0} Log', LOG_OF: '{0} Log',
STATUS_OF: '{0} Status', STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Upload/Download', DOWNLOAD_UPLOAD: 'Download/Upload',
VERSION_ON: 'You are currently on version', VERSION_ON: 'You are currently on version',
CLOSE: 'Close', CLOSE: 'Close',
USE: 'Use', USE: 'Use',
@@ -194,7 +192,6 @@ const en: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events',
DOWNLOAD_SETTINGS_TEXT: 'Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information', DOWNLOAD_SETTINGS_TEXT: 'Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information',
UPLOAD_TEXT: 'Upload a new firmware (.bin) file, settings or customizations (.json) file below, for optional validation upload (.md5) first', UPLOAD_TEXT: 'Upload a new firmware (.bin) file, settings or customizations (.json) file below, for optional validation upload (.md5) first',
UPLOADING: 'Uploading',
UPLOAD_DROP_TEXT: 'Drop file or click here', UPLOAD_DROP_TEXT: 'Drop file or click here',
ERROR: 'Unexpected Error, please try again', ERROR: 'Unexpected Error, please try again',
TIME_SET: 'Time set', TIME_SET: 'Time set',
@@ -252,9 +249,9 @@ const en: Translation = {
TIME_ZONE: 'Time Zone', TIME_ZONE: 'Time Zone',
ACCESS_POINT: 'Access Point', ACCESS_POINT: 'Access Point',
AP_PROVIDE: 'Enable Access Point', AP_PROVIDE: 'Enable Access Point',
AP_PROVIDE_TEXT_1: 'always', AP_PROVIDE_TEXT_1: 'Always',
AP_PROVIDE_TEXT_2: 'when WiFi is disconnected', AP_PROVIDE_TEXT_2: 'When WiFi is disconnected',
AP_PROVIDE_TEXT_3: 'never', AP_PROVIDE_TEXT_3: 'Never',
AP_PREFERRED_CHANNEL: 'Preferred Channel', AP_PREFERRED_CHANNEL: 'Preferred Channel',
AP_HIDE_SSID: 'Hide SSID', AP_HIDE_SSID: 'Hide SSID',
AP_CLIENTS: 'AP Clients', AP_CLIENTS: 'AP Clients',
@@ -325,7 +322,7 @@ const en: Translation = {
SYSTEM_MEMORY: 'System Memory', SYSTEM_MEMORY: 'System Memory',
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings',
SECURITY_1: 'Add or remove users', SECURITY_1: 'Add or remove users',
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware',
MODULES: 'Modules', MODULES: 'Modules',
MODULES_1: 'Activate or deactivate external modules', MODULES_1: 'Activate or deactivate external modules',
MODULES_UPDATED: 'Modules updated', MODULES_UPDATED: 'Modules updated',
@@ -333,7 +330,10 @@ const en: Translation = {
MODULES_NONE: 'No external modules detected', MODULES_NONE: 'No external modules detected',
RENAME: 'Rename', RENAME: 'Rename',
ENABLE_MODBUS: 'Enable Modbus', ENABLE_MODBUS: 'Enable Modbus',
VIEW_LOG: 'View log to diagnose issues' VIEW_LOG: 'View log to diagnose issues',
UPLOAD_DRAG: 'drag and drop a file here or click to select one',
SERVICES: 'Services',
ALLVALUES: 'All Values'
}; };
export default en; export default en;

View File

@@ -94,8 +94,6 @@ const fr: Translation = {
APPLICATION: "l'application", APPLICATION: "l'application",
CUSTOMIZATIONS: 'Personnalisation', CUSTOMIZATIONS: 'Personnalisation',
APPLICATION_RESTARTING: 'EMS-ESP redémarre', APPLICATION_RESTARTING: 'EMS-ESP redémarre',
INTERFACE_BOARD_PROFILE: "Profile de carte d'interface",
BOARD_PROFILE_TEXT: "Sélectionnez un profil de carte d'interface préconfiguré dans la liste ci-dessous ou choisissez Personnalisé pour configurer vos propres paramètres matériels",
BOARD_PROFILE: 'Profil de carte', BOARD_PROFILE: 'Profil de carte',
CUSTOM: 'Personnalisé', CUSTOM: 'Personnalisé',
GPIO_OF: 'GPIO {0}', GPIO_OF: 'GPIO {0}',
@@ -103,7 +101,7 @@ const fr: Translation = {
TEMPERATURE: 'Température', TEMPERATURE: 'Température',
PHY_TYPE: 'Eth PHY Type', PHY_TYPE: 'Eth PHY Type',
DISABLED: 'désactivé', DISABLED: 'désactivé',
TX_MODE: 'Tx Mode', TX_MODE: 'EMS Tx Mode',
HARDWARE: 'Hardware', HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}', EMS_BUS: '{{BUS|EMS BUS}}',
GENERAL_OPTIONS: 'Options générales', GENERAL_OPTIONS: 'Options générales',
@@ -128,7 +126,7 @@ const fr: Translation = {
BOOLEAN_FORMAT_API: 'Format booléen API/MQTT', BOOLEAN_FORMAT_API: 'Format booléen API/MQTT',
ENUM_FORMAT: 'Format enum API/MQTT', ENUM_FORMAT: 'Format enum API/MQTT',
INDEX: 'Index', INDEX: 'Index',
ENABLE_PARASITE: 'Activer la puissance parasite', ENABLE_PARASITE: 'Activer la puissance 1-wire parasite',
LOGGING: 'Journal', LOGGING: 'Journal',
LOG_HEX: 'Enregistrer les télégrammes EMS en hexadécimal', LOG_HEX: 'Enregistrer les télégrammes EMS en hexadécimal',
ENABLE_SYSLOG: 'Activer les logs système', ENABLE_SYSLOG: 'Activer les logs système',
@@ -169,7 +167,7 @@ const fr: Translation = {
SYSTEM: 'Système', SYSTEM: 'Système',
LOG_OF: '{0} Log', LOG_OF: '{0} Log',
STATUS_OF: 'Statut {0}', STATUS_OF: 'Statut {0}',
UPLOAD_DOWNLOAD: 'Upload/Download', DOWNLOAD_UPLOAD: 'Download/Upload', // TODO translate
VERSION_ON: 'You are currently on', // TODO translate VERSION_ON: 'You are currently on', // TODO translate
CLOSE: 'Fermer', CLOSE: 'Fermer',
USE: 'Utiliser', USE: 'Utiliser',
@@ -194,7 +192,6 @@ const fr: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', // TODO translate DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', // TODO translate
DOWNLOAD_SETTINGS_TEXT: "Téléchargez les paramètres de l'application. Soyez prudent lorsque vous partagez vos paramètres car ce fichier contient des mots de passe et d'autres informations système sensibles.", DOWNLOAD_SETTINGS_TEXT: "Téléchargez les paramètres de l'application. Soyez prudent lorsque vous partagez vos paramètres car ce fichier contient des mots de passe et d'autres informations système sensibles.",
UPLOAD_TEXT: "Téléchargez un nouveau fichier de firmware (.bin), un fichier de paramètres ou de personnalisations (.json) ci-dessous, pour une validation optionnelle téléchargez d'abord un fichier (.md5)", UPLOAD_TEXT: "Téléchargez un nouveau fichier de firmware (.bin), un fichier de paramètres ou de personnalisations (.json) ci-dessous, pour une validation optionnelle téléchargez d'abord un fichier (.md5)",
UPLOADING: 'Téléchargement',
UPLOAD_DROP_TEXT: 'Déposer le fichier ou cliquer ici', UPLOAD_DROP_TEXT: 'Déposer le fichier ou cliquer ici',
ERROR: 'Erreur inattendue, veuillez réessayer', ERROR: 'Erreur inattendue, veuillez réessayer',
TIME_SET: 'Time set', TIME_SET: 'Time set',
@@ -325,7 +322,7 @@ const fr: Translation = {
SYSTEM_MEMORY: 'System Memory', // TODO translate SYSTEM_MEMORY: 'System Memory', // TODO translate
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
MODULES: 'Module', // TODO translate MODULES: 'Module', // TODO translate
MODULES_1: 'Activer ou désactiver les modules externes', // TODO translate MODULES_1: 'Activer ou désactiver les modules externes', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate MODULES_UPDATED: 'Modules updated', // TODO translate
@@ -333,7 +330,10 @@ const fr: Translation = {
MODULES_NONE: 'No external modules detected', // TODO translate MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename', // TODO translate RENAME: 'Rename', // TODO translate
ENABLE_MODBUS: 'Activer Modbus', ENABLE_MODBUS: 'Activer Modbus',
VIEW_LOG: 'View log to diagnose issues' // TODO translate VIEW_LOG: 'View log to diagnose issues', // TODO translate
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
SERVICES: 'Services', // TODO translate
ALLVALUES: 'All Values' // TODO translate
}; };
export default fr; export default fr;

View File

@@ -94,8 +94,6 @@ const it: Translation = {
APPLICATION: 'Applicazione', APPLICATION: 'Applicazione',
CUSTOMIZATIONS: 'Personalizzazione', CUSTOMIZATIONS: 'Personalizzazione',
APPLICATION_RESTARTING: 'EMS-ESP sta riavviando', APPLICATION_RESTARTING: 'EMS-ESP sta riavviando',
INTERFACE_BOARD_PROFILE: 'Profilo scheda di interfaccia',
BOARD_PROFILE_TEXT: 'Selezionare un profilo di interfaccia pre-configurato dalla lista sottostante o scegliere un profilo personalizzato per configurare le impostazioni del tuo hardware',
BOARD_PROFILE: 'Profilo Scheda', BOARD_PROFILE: 'Profilo Scheda',
CUSTOM: 'Personalizzazione', CUSTOM: 'Personalizzazione',
GPIO_OF: 'GPIO {0}', GPIO_OF: 'GPIO {0}',
@@ -103,7 +101,7 @@ const it: Translation = {
TEMPERATURE: 'Temperatura', TEMPERATURE: 'Temperatura',
PHY_TYPE: 'Eth PHY Type', PHY_TYPE: 'Eth PHY Type',
DISABLED: 'disattivato', DISABLED: 'disattivato',
TX_MODE: 'Modo Tx ', TX_MODE: 'EMS Modo Tx ',
HARDWARE: 'Hardware', HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}', EMS_BUS: '{{BUS|EMS BUS}}',
GENERAL_OPTIONS: 'Opzioni Generali', GENERAL_OPTIONS: 'Opzioni Generali',
@@ -128,7 +126,7 @@ const it: Translation = {
BOOLEAN_FORMAT_API: 'Formato booleano API/MQTT', BOOLEAN_FORMAT_API: 'Formato booleano API/MQTT',
ENUM_FORMAT: 'Enum Format API/MQTT', ENUM_FORMAT: 'Enum Format API/MQTT',
INDEX: 'Indice', INDEX: 'Indice',
ENABLE_PARASITE: 'Abilita potenza parassita', ENABLE_PARASITE: 'Abilita potenza 1-wire parassita',
LOGGING: 'Registrazione', LOGGING: 'Registrazione',
LOG_HEX: 'Registra telegrammi EMS in esadecimale', LOG_HEX: 'Registra telegrammi EMS in esadecimale',
ENABLE_SYSLOG: 'Abilita Syslog', ENABLE_SYSLOG: 'Abilita Syslog',
@@ -169,7 +167,7 @@ const it: Translation = {
SYSTEM: 'Sistema', SYSTEM: 'Sistema',
LOG_OF: 'Registro {0}', LOG_OF: 'Registro {0}',
STATUS_OF: 'Stato {0}', STATUS_OF: 'Stato {0}',
UPLOAD_DOWNLOAD: 'Caricamento/Scaricamento', DOWNLOAD_UPLOAD: 'Scaricamento/Caricamento',
VERSION_ON: 'Attualmente stai eseguendo la versione', VERSION_ON: 'Attualmente stai eseguendo la versione',
CLOSE: 'Chiudere', CLOSE: 'Chiudere',
USE: 'Usa', USE: 'Usa',
@@ -194,7 +192,6 @@ const it: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events',
DOWNLOAD_SETTINGS_TEXT: 'Scarica le impostazioni dell applicazione. Fai attenzione quando condividi le tue impostazioni poiché questo file contiene password e altre informazioni di sistema riservate', DOWNLOAD_SETTINGS_TEXT: 'Scarica le impostazioni dell applicazione. Fai attenzione quando condividi le tue impostazioni poiché questo file contiene password e altre informazioni di sistema riservate',
UPLOAD_TEXT: 'Carica un nuovo file firmware (.bin) , file delle impostazioni o delle personalizzazioni (.json) di seguito, per un opzione di convalida scaricare dapprima un file "*.MD5" ', UPLOAD_TEXT: 'Carica un nuovo file firmware (.bin) , file delle impostazioni o delle personalizzazioni (.json) di seguito, per un opzione di convalida scaricare dapprima un file "*.MD5" ',
UPLOADING: 'Caricamento',
UPLOAD_DROP_TEXT: 'Trascina il file o clicca qui', UPLOAD_DROP_TEXT: 'Trascina il file o clicca qui',
ERROR: 'Errore Inaspettato, prego tenta ancora', ERROR: 'Errore Inaspettato, prego tenta ancora',
TIME_SET: 'Imposta Ora', TIME_SET: 'Imposta Ora',
@@ -325,7 +322,7 @@ const it: Translation = {
SYSTEM_MEMORY: 'System Memory', // TODO translate SYSTEM_MEMORY: 'System Memory', // TODO translate
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
MODULES: 'Module', // TODO translate MODULES: 'Module', // TODO translate
MODULES_1: 'Attiva o disattiva i moduli esterni', // TODO translate MODULES_1: 'Attiva o disattiva i moduli esterni', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate MODULES_UPDATED: 'Modules updated', // TODO translate
@@ -333,7 +330,10 @@ const it: Translation = {
MODULES_NONE: 'No external modules detected', // TODO translate MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename', // TODO translate RENAME: 'Rename', // TODO translate
ENABLE_MODBUS: 'Abilita Modbus', ENABLE_MODBUS: 'Abilita Modbus',
VIEW_LOG: 'View log to diagnose issues' // TODO translate VIEW_LOG: 'View log to diagnose issues', // TODO translate
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
SERVICES: 'Services', // TODO translate
ALLVALUES: 'All Values' // TODO translate
}; };
export default it; export default it;

View File

@@ -94,15 +94,13 @@ const nl: Translation = {
APPLICATION: 'Applicatie', APPLICATION: 'Applicatie',
CUSTOMIZATIONS: 'Aanpassingen van entiteiten', CUSTOMIZATIONS: 'Aanpassingen van entiteiten',
APPLICATION_RESTARTING: 'EMS-ESP herstarten', APPLICATION_RESTARTING: 'EMS-ESP herstarten',
INTERFACE_BOARD_PROFILE: 'Interface Apparaatprofiel',
BOARD_PROFILE_TEXT: 'Selecteer een vooraf ingesteld apparaat profiel uit de lijst of kies Eigen om zelf uw hardware te configureren',
BOARD_PROFILE: 'Apparaatprofiel', BOARD_PROFILE: 'Apparaatprofiel',
CUSTOM: 'Custom', CUSTOM: 'Custom',
GPIO_OF: '{0} GPIO', GPIO_OF: '{0} GPIO',
BUTTON: 'Toets', BUTTON: 'Toets',
TEMPERATURE: 'Temperatuur', TEMPERATURE: 'Temperatuur',
PHY_TYPE: 'Eth PHY Type', PHY_TYPE: 'Eth PHY Type',
TX_MODE: 'Tx Mode', TX_MODE: 'EMS Tx Mode',
HARDWARE: 'Hardware', HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}', EMS_BUS: '{{BUS|EMS BUS}}',
DISABLED: 'Uitgeschakeld', DISABLED: 'Uitgeschakeld',
@@ -124,11 +122,11 @@ const nl: Translation = {
TRIGGER_TIME: 'Trigger tijd', TRIGGER_TIME: 'Trigger tijd',
COLD_SHOT_DURATION: 'Tijd Shot koud water', COLD_SHOT_DURATION: 'Tijd Shot koud water',
FORMATTING_OPTIONS: 'Formatteringsopties', FORMATTING_OPTIONS: 'Formatteringsopties',
BOOLEAN_FORMAT_DASHBOARD: 'Boolean formaat dashboard', BOOLEAN_FORMAT_DASHBOARD: 'Boolean formaat web',
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 parasitaire modus', ENABLE_PARASITE: 'Activeer 1-wire 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',
@@ -169,7 +167,7 @@ const nl: Translation = {
SYSTEM: 'Systeem', SYSTEM: 'Systeem',
LOG_OF: '{0} Log', LOG_OF: '{0} Log',
STATUS_OF: '{0} Status', STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Upload/Download', DOWNLOAD_UPLOAD: 'Download/Upload',
VERSION_ON: 'U bevindt zich momenteel op versie', VERSION_ON: 'U bevindt zich momenteel op versie',
CLOSE: 'Sluiten', CLOSE: 'Sluiten',
USE: 'Gebruik', USE: 'Gebruik',
@@ -194,7 +192,6 @@ const nl: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events',
DOWNLOAD_SETTINGS_TEXT: 'Download de applicatie settings. Wees voorzichting met het delen van dit bestand want het bevat o.a. de wachtwoorden in plain text', DOWNLOAD_SETTINGS_TEXT: 'Download de applicatie settings. Wees voorzichting met het delen van dit bestand want het bevat o.a. de wachtwoorden in plain text',
UPLOAD_TEXT: 'Upload een nieuwe firmware (.bin) file, instellingen of custom instellingen (.json) bestand hieronder', UPLOAD_TEXT: 'Upload een nieuwe firmware (.bin) file, instellingen of custom instellingen (.json) bestand hieronder',
UPLOADING: 'Uploading',
UPLOAD_DROP_TEXT: 'Sleep bestand hierheen of klik hier', UPLOAD_DROP_TEXT: 'Sleep bestand hierheen of klik hier',
ERROR: 'Onverwachte fout, probeer opnieuw', ERROR: 'Onverwachte fout, probeer opnieuw',
TIME_SET: 'Tijd ingesteld', TIME_SET: 'Tijd ingesteld',
@@ -325,7 +322,7 @@ const nl: Translation = {
SYSTEM_MEMORY: 'System Geheugen', SYSTEM_MEMORY: 'System Geheugen',
APPLICATION_SETTINGS_1: 'Applicatie-instellingen wijzigen', APPLICATION_SETTINGS_1: 'Applicatie-instellingen wijzigen',
SECURITY_1: 'Gebruikers toevoegen of verwijderen', SECURITY_1: 'Gebruikers toevoegen of verwijderen',
UPLOAD_DOWNLOAD_1: 'Upload-/downloadinstellingen en firmware', DOWNLOAD_UPLOAD_1: 'Download en upload instellingen en firmware',
MODULES: 'Module', MODULES: 'Module',
MODULES_1: 'Externe modules activeren of deactiveren', // TODO translate MODULES_1: 'Externe modules activeren of deactiveren', // TODO translate
MODULES_UPDATED: 'Modules geüpdatet', MODULES_UPDATED: 'Modules geüpdatet',
@@ -333,7 +330,10 @@ const nl: Translation = {
MODULES_NONE: 'Geen externe modules gedetecteerd', MODULES_NONE: 'Geen externe modules gedetecteerd',
RENAME: 'Hernoemen', RENAME: 'Hernoemen',
ENABLE_MODBUS: 'Activeer Modbus', ENABLE_MODBUS: 'Activeer Modbus',
VIEW_LOG: 'View log to diagnose issues' // TODO translate VIEW_LOG: 'View log to diagnose issues', // TODO translate
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
SERVICES: 'Services', // TODO translate
ALLVALUES: 'All Values' // TODO translate
}; };
export default nl; export default nl;

View File

@@ -94,8 +94,6 @@ const no: Translation = {
APPLICATION: 'Søknad', APPLICATION: 'Søknad',
CUSTOMIZATIONS: 'Tilpasninger', CUSTOMIZATIONS: 'Tilpasninger',
APPLICATION_RESTARTING: 'EMS-ESP restarter', APPLICATION_RESTARTING: 'EMS-ESP restarter',
INTERFACE_BOARD_PROFILE: 'Interface Prosessor Profil',
BOARD_PROFILE_TEXT: 'Velg en pre-konfigurert prosessor profil fra listen under eller velg Tilpasset for å konfigurere dine egne innstillinger',
BOARD_PROFILE: 'Prosessor Profil', BOARD_PROFILE: 'Prosessor Profil',
CUSTOM: 'Custom', CUSTOM: 'Custom',
GPIO_OF: '{0} GPIO', GPIO_OF: '{0} GPIO',
@@ -103,7 +101,7 @@ const no: Translation = {
TEMPERATURE: 'Temperatur', TEMPERATURE: 'Temperatur',
PHY_TYPE: 'Eth PHY Type', PHY_TYPE: 'Eth PHY Type',
DISABLED: 'avslått', DISABLED: 'avslått',
TX_MODE: 'Tx Mode', TX_MODE: 'EMS Tx Mode',
HARDWARE: 'Hardware', HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}', EMS_BUS: '{{BUS|EMS BUS}}',
GENERAL_OPTIONS: 'Generelle Innstillinger', GENERAL_OPTIONS: 'Generelle Innstillinger',
@@ -124,11 +122,11 @@ const no: Translation = {
TRIGGER_TIME: 'Aktiveringstid', TRIGGER_TIME: 'Aktiveringstid',
COLD_SHOT_DURATION: 'Tid på kaldt vann', COLD_SHOT_DURATION: 'Tid på kaldt vann',
FORMATTING_OPTIONS: 'Formatteringsalternativs', FORMATTING_OPTIONS: 'Formatteringsalternativs',
BOOLEAN_FORMAT_DASHBOARD: 'Bool Format Dashboard', BOOLEAN_FORMAT_DASHBOARD: 'Bool Format Web',
BOOLEAN_FORMAT_API: 'Bool Format API/MQTT', BOOLEAN_FORMAT_API: 'Bool Format API/MQTT',
ENUM_FORMAT: 'Enum Format API/MQTT', ENUM_FORMAT: 'Enum Format API/MQTT',
INDEX: 'Indeks', INDEX: 'Indeks',
ENABLE_PARASITE: 'Aktiver parasitt strømforsyning', ENABLE_PARASITE: 'Aktiver 1-wire parasitt strømforsyning',
LOGGING: 'Logging', LOGGING: 'Logging',
LOG_HEX: 'Logg EMS telegrammer i hexadesimal', LOG_HEX: 'Logg EMS telegrammer i hexadesimal',
ENABLE_SYSLOG: 'Aktiver Syslog', ENABLE_SYSLOG: 'Aktiver Syslog',
@@ -169,7 +167,7 @@ const no: Translation = {
SYSTEM: 'System', SYSTEM: 'System',
LOG_OF: '{0} Logg', LOG_OF: '{0} Logg',
STATUS_OF: '{0} Status', STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Opp/Nedlasting', DOWNLOAD_UPLOAD: 'Nedlasting/Opp',
VERSION_ON: 'You are currently on', // TODO translate VERSION_ON: 'You are currently on', // TODO translate
CLOSE: 'Steng', CLOSE: 'Steng',
USE: 'Bruk', USE: 'Bruk',
@@ -194,7 +192,6 @@ const no: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Last ned planlagte oppgaver', DOWNLOAD_SCHEDULE_TEXT: 'Last ned planlagte oppgaver',
DOWNLOAD_SETTINGS_TEXT: 'Last ned applikasjonskonfigurasjon. Vær varsom med å dele fila da den inneholder passord og annen sensitiv system informasjon', DOWNLOAD_SETTINGS_TEXT: 'Last ned applikasjonskonfigurasjon. Vær varsom med å dele fila da den inneholder passord og annen sensitiv system informasjon',
UPLOAD_TEXT: 'Last opp en ny firmware (.bin) fil, innstillinger eller tilpassninger (.json) fil nedenfor', UPLOAD_TEXT: 'Last opp en ny firmware (.bin) fil, innstillinger eller tilpassninger (.json) fil nedenfor',
UPLOADING: 'Opplasting',
UPLOAD_DROP_TEXT: 'Slipp fil eller klikk her', UPLOAD_DROP_TEXT: 'Slipp fil eller klikk her',
ERROR: 'Ukjent feil, prøv igjen', ERROR: 'Ukjent feil, prøv igjen',
TIME_SET: 'Still in tid', TIME_SET: 'Still in tid',
@@ -325,7 +322,7 @@ const no: Translation = {
SYSTEM_MEMORY: 'System Memory', // TODO translate SYSTEM_MEMORY: 'System Memory', // TODO translate
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
MODULES: 'Module', // TODO translate MODULES: 'Module', // TODO translate
MODULES_1: 'Aktiver eller deaktiver eksterne moduler', // TODO translate MODULES_1: 'Aktiver eller deaktiver eksterne moduler', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate MODULES_UPDATED: 'Modules updated', // TODO translate
@@ -333,7 +330,10 @@ const no: Translation = {
MODULES_NONE: 'No external modules detected', // TODO translate MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename', // TODO translate RENAME: 'Rename', // TODO translate
ENABLE_MODBUS: 'Aktiver Modbus', ENABLE_MODBUS: 'Aktiver Modbus',
VIEW_LOG: 'View log to diagnose issues' // TODO translate VIEW_LOG: 'View log to diagnose issues', // TODO translate
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
SERVICES: 'Services', // TODO translate
ALLVALUES: 'All Values' // TODO translate
}; };
export default no; export default no;

View File

@@ -94,8 +94,6 @@ const pl: BaseTranslation = {
APPLICATION: 'Aplikacji', APPLICATION: 'Aplikacji',
CUSTOMIZATIONS: 'Personalizacja', CUSTOMIZATIONS: 'Personalizacja',
APPLICATION_RESTARTING: 'Trwa ponowne uruchamianie', APPLICATION_RESTARTING: 'Trwa ponowne uruchamianie',
INTERFACE_BOARD_PROFILE: 'Profil płytki interfejsu',
BOARD_PROFILE_TEXT: 'Wybierz z listy gotowy profil płytki interfejsu lub "własny..." i samodzielnie skonfiguruj posiadany sprzęt.',
BOARD_PROFILE: 'Profil płytki', BOARD_PROFILE: 'Profil płytki',
CUSTOM: 'własny', CUSTOM: 'własny',
GPIO_OF: 'GPIO {0}', GPIO_OF: 'GPIO {0}',
@@ -103,7 +101,7 @@ const pl: BaseTranslation = {
TEMPERATURE: '1-Wire®', TEMPERATURE: '1-Wire®',
PHY_TYPE: 'Typ układu ethernetowego (PHY)', PHY_TYPE: 'Typ układu ethernetowego (PHY)',
DISABLED: '{{wyłączono|brak|}}', DISABLED: '{{wyłączono|brak|}}',
TX_MODE: 'Tryb transmisji (Tx)', TX_MODE: 'EMS Tryb transmisji (Tx)',
EMS_BUS: '{{magistrali EMS|na magistrali|}}', EMS_BUS: '{{magistrali EMS|na magistrali|}}',
HARDWARE: 'sprzętowy', HARDWARE: 'sprzętowy',
GENERAL_OPTIONS: 'Opcje podstawowe', GENERAL_OPTIONS: 'Opcje podstawowe',
@@ -128,7 +126,7 @@ const pl: BaseTranslation = {
BOOLEAN_FORMAT_API: 'Wartości dwustanowe w API/MQTT', BOOLEAN_FORMAT_API: 'Wartości dwustanowe w API/MQTT',
ENUM_FORMAT: 'Wartości z listy w API/MQTT', ENUM_FORMAT: 'Wartości z listy w API/MQTT',
INDEX: 'indeks', INDEX: 'indeks',
ENABLE_PARASITE: 'Aktywuj zasilanie pasożytnicze', ENABLE_PARASITE: 'Aktywuj zasilanie 1-wire pasożytnicze',
LOGGING: 'Logowanie', LOGGING: 'Logowanie',
LOG_HEX: 'Loguj telegramy EMS w systemie szesnastkowym (hex)', LOG_HEX: 'Loguj telegramy EMS w systemie szesnastkowym (hex)',
ENABLE_SYSLOG: 'Aktywuj SysLog', ENABLE_SYSLOG: 'Aktywuj SysLog',
@@ -169,7 +167,7 @@ const pl: BaseTranslation = {
SYSTEM: '{{S|s||s}}yste{{m|mu||mowy}}', SYSTEM: '{{S|s||s}}yste{{m|mu||mowy}}',
LOG_OF: 'Log {0}', LOG_OF: 'Log {0}',
STATUS_OF: 'Status {0}', STATUS_OF: 'Status {0}',
UPLOAD_DOWNLOAD: 'Przesyłanie plików', DOWNLOAD_UPLOAD: 'Plików przesyłanie',
VERSION_ON: 'Aktualnie używasz', VERSION_ON: 'Aktualnie używasz',
CLOSE: 'Zamknij', CLOSE: 'Zamknij',
USE: 'Aby zaktualizować firmware skorzystaj z funkcji', USE: 'Aby zaktualizować firmware skorzystaj z funkcji',
@@ -194,7 +192,6 @@ const pl: BaseTranslation = {
DOWNLOAD_SCHEDULE_TEXT: 'Pobierz harmonogram zdarzeń.', DOWNLOAD_SCHEDULE_TEXT: 'Pobierz harmonogram zdarzeń.',
DOWNLOAD_SETTINGS_TEXT: 'Pobierz ustawienia aplikacji. Uwaga! Plik z ustawieniami zawiera hasła oraz inne wrażliwe informacje systemowe! Nie udostepniaj go pochopnie!', DOWNLOAD_SETTINGS_TEXT: 'Pobierz ustawienia aplikacji. Uwaga! Plik z ustawieniami zawiera hasła oraz inne wrażliwe informacje systemowe! Nie udostepniaj go pochopnie!',
UPLOAD_TEXT: 'Wyślij firmware (.bin), ustawienia lub personalizacje (.json). Opcjonalnie, wyślij wcześniej plik walidacji z sumą kontrolną (.md5).', UPLOAD_TEXT: 'Wyślij firmware (.bin), ustawienia lub personalizacje (.json). Opcjonalnie, wyślij wcześniej plik walidacji z sumą kontrolną (.md5).',
UPLOADING: 'Wysłano',
UPLOAD_DROP_TEXT: 'Przeciągnij tutaj plik lub kliknij', UPLOAD_DROP_TEXT: 'Przeciągnij tutaj plik lub kliknij',
ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!', ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!',
TIME_SET: 'Zegar został ustawiony.', TIME_SET: 'Zegar został ustawiony.',
@@ -325,7 +322,7 @@ const pl: BaseTranslation = {
SYSTEM_MEMORY: 'Pamięć systemowa', SYSTEM_MEMORY: 'Pamięć systemowa',
APPLICATION_SETTINGS_1: 'Modyfikacja ustawień aplikacji EMS-ESP', APPLICATION_SETTINGS_1: 'Modyfikacja ustawień aplikacji EMS-ESP',
SECURITY_1: 'Dodawanie i usuwanie użytkowników', SECURITY_1: 'Dodawanie i usuwanie użytkowników',
UPLOAD_DOWNLOAD_1: 'Wysyłanie/pobieranie ustawień i firmware', DOWNLOAD_UPLOAD_1: 'Pobieranie/wysyłanie ustawień i firmware',
MODULES: 'Module', // TODO translate MODULES: 'Module', // TODO translate
MODULES_1: 'Aktywuj lub dezaktywuj moduły zewnętrzne', // TODO translate MODULES_1: 'Aktywuj lub dezaktywuj moduły zewnętrzne', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate MODULES_UPDATED: 'Modules updated', // TODO translate
@@ -333,7 +330,10 @@ const pl: BaseTranslation = {
MODULES_NONE: 'No external modules detected', // TODO translate MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename', // TODO translate RENAME: 'Rename', // TODO translate
ENABLE_MODBUS: 'Aktywuj Modbus', ENABLE_MODBUS: 'Aktywuj Modbus',
VIEW_LOG: 'View log to diagnose issues' // TODO translate VIEW_LOG: 'View log to diagnose issues', // TODO translate
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
SERVICES: 'Services', // TODO translate
ALLVALUES: 'All Values' // TODO translate
}; };
export default pl; export default pl;

View File

@@ -94,8 +94,6 @@ const sk: Translation = {
APPLICATION: 'Aplikácie', APPLICATION: 'Aplikácie',
CUSTOMIZATIONS: 'Prispôsobenia', CUSTOMIZATIONS: 'Prispôsobenia',
APPLICATION_RESTARTING: 'EMS-ESP sa reštartuje', APPLICATION_RESTARTING: 'EMS-ESP sa reštartuje',
INTERFACE_BOARD_PROFILE: 'Profil dosky rozhrania',
BOARD_PROFILE_TEXT: 'Vyberte vopred nakonfigurovaný profil dosky rozhrania zo zoznamu nižšie, alebo vyberte možnosť Vlastné a nakonfigurujte svoje vlastné hardvérové nastavenia',
BOARD_PROFILE: 'Profil dosky', BOARD_PROFILE: 'Profil dosky',
CUSTOM: 'Vlastné', CUSTOM: 'Vlastné',
GPIO_OF: '{0} GPIO', GPIO_OF: '{0} GPIO',
@@ -103,7 +101,7 @@ const sk: Translation = {
TEMPERATURE: 'Teplota', TEMPERATURE: 'Teplota',
PHY_TYPE: 'Eth PHY Typ', PHY_TYPE: 'Eth PHY Typ',
DISABLED: 'zakázané', DISABLED: 'zakázané',
TX_MODE: 'Tx režim', TX_MODE: 'EMS Tx režim',
HARDWARE: 'Hardware', HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}', EMS_BUS: '{{BUS|EMS BUS}}',
GENERAL_OPTIONS: 'Všeobecné možnosti', GENERAL_OPTIONS: 'Všeobecné možnosti',
@@ -124,11 +122,11 @@ const sk: Translation = {
TRIGGER_TIME: 'Čas spustenia', TRIGGER_TIME: 'Čas spustenia',
COLD_SHOT_DURATION: 'Trvanie studeného záberu', COLD_SHOT_DURATION: 'Trvanie studeného záberu',
FORMATTING_OPTIONS: 'Možnosti formátovania', FORMATTING_OPTIONS: 'Možnosti formátovania',
BOOLEAN_FORMAT_DASHBOARD: 'Panel Boolean formát', BOOLEAN_FORMAT_DASHBOARD: 'Web panel Boolean formát',
BOOLEAN_FORMAT_API: 'Boolean formát API/MQTT', BOOLEAN_FORMAT_API: 'Boolean formát API/MQTT',
ENUM_FORMAT: 'Enum formát API/MQTT', ENUM_FORMAT: 'Enum formát API/MQTT',
INDEX: 'Index', INDEX: 'Index',
ENABLE_PARASITE: 'Povoliť parazité napájanie DS18B20', ENABLE_PARASITE: 'Povoliť 1-wire parazité napájanie DS18B20',
LOGGING: 'Logovanie', LOGGING: 'Logovanie',
LOG_HEX: 'Záznam telegramov EMS v hexadecimálnej sústave', LOG_HEX: 'Záznam telegramov EMS v hexadecimálnej sústave',
ENABLE_SYSLOG: 'Povoliť Syslog', ENABLE_SYSLOG: 'Povoliť Syslog',
@@ -169,7 +167,7 @@ const sk: Translation = {
SYSTEM: 'Systém', SYSTEM: 'Systém',
LOG_OF: '{0} Log', LOG_OF: '{0} Log',
STATUS_OF: '{0} Stav', STATUS_OF: '{0} Stav',
UPLOAD_DOWNLOAD: 'Nahrať/Stiahnuť', DOWNLOAD_UPLOAD: 'Stiahnuť/Nahrať',
VERSION_ON: 'Momentálne nainštalovaná verzia: ', VERSION_ON: 'Momentálne nainštalovaná verzia: ',
CLOSE: 'Zatvoriť', CLOSE: 'Zatvoriť',
USE: 'Použiť', USE: 'Použiť',
@@ -194,7 +192,6 @@ const sk: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Stiahnutie plánovača udalostí', DOWNLOAD_SCHEDULE_TEXT: 'Stiahnutie plánovača udalostí',
DOWNLOAD_SETTINGS_TEXT: 'Stiahnite si nastavenia aplikácie. Pri zdieľaní nastavení buďte opatrní, pretože tento súbor obsahuje heslá a iné citlivé systémové informácie.', DOWNLOAD_SETTINGS_TEXT: 'Stiahnite si nastavenia aplikácie. Pri zdieľaní nastavení buďte opatrní, pretože tento súbor obsahuje heslá a iné citlivé systémové informácie.',
UPLOAD_TEXT: 'Najskôr nahrajte nový súbor firmvéru (.bin), nastavenia alebo prispôsobenia (.json), pre voliteľné overenie nahrajte súbor (.md5)', UPLOAD_TEXT: 'Najskôr nahrajte nový súbor firmvéru (.bin), nastavenia alebo prispôsobenia (.json), pre voliteľné overenie nahrajte súbor (.md5)',
UPLOADING: 'Nahrávanie',
UPLOAD_DROP_TEXT: 'Potiahnúť a pripnúť súbor alebo kliknúť sem', UPLOAD_DROP_TEXT: 'Potiahnúť a pripnúť súbor alebo kliknúť sem',
ERROR: 'Neočakávaná chyba, prosím skúste to znova', ERROR: 'Neočakávaná chyba, prosím skúste to znova',
TIME_SET: 'Nastavený čas', TIME_SET: 'Nastavený čas',
@@ -325,7 +322,7 @@ const sk: Translation = {
SYSTEM_MEMORY: 'System Memory', // TODO translate SYSTEM_MEMORY: 'System Memory', // TODO translate
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
MODULES: 'Module', // TODO translate MODULES: 'Module', // TODO translate
MODULES_1: 'Aktivujte alebo deaktivujte externé moduly', // TODO translate MODULES_1: 'Aktivujte alebo deaktivujte externé moduly', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate MODULES_UPDATED: 'Modules updated', // TODO translate
@@ -333,7 +330,10 @@ const sk: Translation = {
MODULES_NONE: 'No external modules detected', // TODO translate MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename', // TODO translate RENAME: 'Rename', // TODO translate
ENABLE_MODBUS: 'Povoliť Modbus', ENABLE_MODBUS: 'Povoliť Modbus',
VIEW_LOG: 'View log to diagnose issues' // TODO translate VIEW_LOG: 'View log to diagnose issues', // TODO translate
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
SERVICES: 'Services', // TODO translate
ALLVALUES: 'All Values' // TODO translate
}; };
export default sk; export default sk;

View File

@@ -94,8 +94,6 @@ const sv: Translation = {
APPLICATION: 'Apliká', APPLICATION: 'Apliká',
CUSTOMIZATIONS: 'Anpassningr', CUSTOMIZATIONS: 'Anpassningr',
APPLICATION_RESTARTING: 'EMS-ESP startar om', APPLICATION_RESTARTING: 'EMS-ESP startar om',
INTERFACE_BOARD_PROFILE: 'Interface Hårdvaruprofil',
BOARD_PROFILE_TEXT: 'Välj en förkonfigurerad hårdvaruprofil från listan nedan eller välj Anpassad för att konfigurera dina egna hårdvaruinställningar',
BOARD_PROFILE: 'Hårdvarutyp', BOARD_PROFILE: 'Hårdvarutyp',
CUSTOM: 'Anpassa', CUSTOM: 'Anpassa',
GPIO_OF: '{0} GPIO', GPIO_OF: '{0} GPIO',
@@ -103,7 +101,7 @@ const sv: Translation = {
TEMPERATURE: 'Temperatur', TEMPERATURE: 'Temperatur',
PHY_TYPE: 'Eth PHY-typ', PHY_TYPE: 'Eth PHY-typ',
DISABLED: 'inaktiverad', DISABLED: 'inaktiverad',
TX_MODE: 'Tx-läge', TX_MODE: 'EMS Tx-läge',
HARDWARE: 'Hårdvara', HARDWARE: 'Hårdvara',
EMS_BUS: '{{BUSS|EMS-BUSS}}', EMS_BUS: '{{BUSS|EMS-BUSS}}',
GENERAL_OPTIONS: 'Allmänna Inställningar', GENERAL_OPTIONS: 'Allmänna Inställningar',
@@ -128,7 +126,7 @@ const sv: Translation = {
BOOLEAN_FORMAT_API: 'Bool-format API/MQTT', BOOLEAN_FORMAT_API: 'Bool-format API/MQTT',
ENUM_FORMAT: 'Enum-format API/MQTT', ENUM_FORMAT: 'Enum-format API/MQTT',
INDEX: 'Index', INDEX: 'Index',
ENABLE_PARASITE: 'Aktivera parasitström', ENABLE_PARASITE: 'Aktivera 1-wire parasitström',
LOGGING: 'Loggning', LOGGING: 'Loggning',
LOG_HEX: 'Logga EMS-telegram i hexadecimal', LOG_HEX: 'Logga EMS-telegram i hexadecimal',
ENABLE_SYSLOG: 'Aktivera Syslog', ENABLE_SYSLOG: 'Aktivera Syslog',
@@ -169,7 +167,7 @@ const sv: Translation = {
SYSTEM: 'System', SYSTEM: 'System',
LOG_OF: '{0} Logg', LOG_OF: '{0} Logg',
STATUS_OF: '{0} Status', STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Upp/Nedladdning', DOWNLOAD_UPLOAD: 'Nedladdning/Upp',
VERSION_ON: 'You are currently on', // TODO translate VERSION_ON: 'You are currently on', // TODO translate
CLOSE: 'Stäng', CLOSE: 'Stäng',
USE: 'Använd', USE: 'Använd',
@@ -194,7 +192,6 @@ const sv: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', // TODO translate DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', // TODO translate
DOWNLOAD_SETTINGS_TEXT: 'Ladda ner applikationsinställningar. Var försiktig om du delar dina iställlningar då de innehåller lösenord och annan känslig systeminformation', DOWNLOAD_SETTINGS_TEXT: 'Ladda ner applikationsinställningar. Var försiktig om du delar dina iställlningar då de innehåller lösenord och annan känslig systeminformation',
UPLOAD_TEXT: 'Ladda upp ett nytt firmware (.bin), inställningar eller anpassningar (.json) nedan', UPLOAD_TEXT: 'Ladda upp ett nytt firmware (.bin), inställningar eller anpassningar (.json) nedan',
UPLOADING: 'Laddar upp',
UPLOAD_DROP_TEXT: 'Släpp fil eller klicka här', UPLOAD_DROP_TEXT: 'Släpp fil eller klicka här',
ERROR: 'Okänt Fel, var god försök igen', ERROR: 'Okänt Fel, var god försök igen',
TIME_SET: 'Ställ in tid', TIME_SET: 'Ställ in tid',
@@ -325,7 +322,7 @@ const sv: Translation = {
SYSTEM_MEMORY: 'System Memory', // TODO translate SYSTEM_MEMORY: 'System Memory', // TODO translate
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
MODULES: 'Module', // TODO translate MODULES: 'Module', // TODO translate
MODULES_1: 'Aktivera eller avaktivera externa moduler', // TODO translate MODULES_1: 'Aktivera eller avaktivera externa moduler', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate MODULES_UPDATED: 'Modules updated', // TODO translate
@@ -333,7 +330,10 @@ const sv: Translation = {
MODULES_NONE: 'No external modules detected', // TODO translate MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename', // TODO translate RENAME: 'Rename', // TODO translate
ENABLE_MODBUS: 'Aktivera Modbus', ENABLE_MODBUS: 'Aktivera Modbus',
VIEW_LOG: 'View log to diagnose issues' // TODO translate VIEW_LOG: 'View log to diagnose issues', // TODO translate
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
SERVICES: 'Services', // TODO translate
ALLVALUES: 'All Values' // TODO translate
}; };
export default sv; export default sv;

View File

@@ -94,8 +94,6 @@ const tr: Translation = {
APPLICATION: 'Uygulama', APPLICATION: 'Uygulama',
CUSTOMIZATIONS: 'Özelleştirme', CUSTOMIZATIONS: 'Özelleştirme',
APPLICATION_RESTARTING: 'EMS-ESP yeniden başlatılıyor', APPLICATION_RESTARTING: 'EMS-ESP yeniden başlatılıyor',
INTERFACE_BOARD_PROFILE: 'Arabirim Kart Profili',
BOARD_PROFILE_TEXT: 'Aşağıdan hazır kart profillerinden birini seçin yada kendi donanımınızı ayarlamak için Özeli tercih edin',
BOARD_PROFILE: 'Kart Profili', BOARD_PROFILE: 'Kart Profili',
CUSTOM: 'Özel', CUSTOM: 'Özel',
GPIO_OF: '{0} GPIO', GPIO_OF: '{0} GPIO',
@@ -103,7 +101,7 @@ const tr: Translation = {
TEMPERATURE: 'Sıcaklık', TEMPERATURE: 'Sıcaklık',
PHY_TYPE: 'Eth PHY Tipi', PHY_TYPE: 'Eth PHY Tipi',
DISABLED: 'devre dışı', DISABLED: 'devre dışı',
TX_MODE: 'Tx Modu', TX_MODE: 'EMS Tx Modu',
HARDWARE: 'Donanım', HARDWARE: 'Donanım',
EMS_BUS: '{{HAT|EMS HATTI}}', EMS_BUS: '{{HAT|EMS HATTI}}',
GENERAL_OPTIONS: 'Genel Seçenekler', GENERAL_OPTIONS: 'Genel Seçenekler',
@@ -128,7 +126,7 @@ const tr: Translation = {
BOOLEAN_FORMAT_API: 'Boolean Biçimleme API/MQTT', BOOLEAN_FORMAT_API: 'Boolean Biçimleme API/MQTT',
ENUM_FORMAT: 'Enum Biçimleme API/MQTT', ENUM_FORMAT: 'Enum Biçimleme API/MQTT',
INDEX: 'İndeks', INDEX: 'İndeks',
ENABLE_PARASITE: 'Parazit gücü devreye al', ENABLE_PARASITE: '1-wire parazit gücü devreye al',
LOGGING: 'Kayıt ediliyor', LOGGING: 'Kayıt ediliyor',
LOG_HEX: 'EMS telegramlarını hexadecimal olarak kayıt et', LOG_HEX: 'EMS telegramlarını hexadecimal olarak kayıt et',
ENABLE_SYSLOG: 'Sistem Kaydını Devreye Al', ENABLE_SYSLOG: 'Sistem Kaydını Devreye Al',
@@ -169,7 +167,7 @@ const tr: Translation = {
SYSTEM: 'Sistem', SYSTEM: 'Sistem',
LOG_OF: '{0} Kaydı', LOG_OF: '{0} Kaydı',
STATUS_OF: '{0} Durumu', STATUS_OF: '{0} Durumu',
UPLOAD_DOWNLOAD: 'Yükleme/İndirme', DOWNLOAD_UPLOAD: 'İndirme/Yükleme',
VERSION_ON: 'You are currently on', // TODO translate VERSION_ON: 'You are currently on', // TODO translate
CLOSE: 'Kapat', CLOSE: 'Kapat',
USE: 'KUllan', USE: 'KUllan',
@@ -194,7 +192,6 @@ const tr: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', // TODO translate DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', // TODO translate
DOWNLOAD_SETTINGS_TEXT: 'Uygulama ayarlarını indir. Bu dosya hassas sistem bilgileri ve şifrelerinizi içerdiğinden ayarlarınızı paylaşırken dikkatli olun', DOWNLOAD_SETTINGS_TEXT: 'Uygulama ayarlarını indir. Bu dosya hassas sistem bilgileri ve şifrelerinizi içerdiğinden ayarlarınızı paylaşırken dikkatli olun',
UPLOAD_TEXT: 'Yeni bir bellenim(.bin) dosyası yükleyin, ayarlar ve özelleştirmeler(.json) dosyası aşağıda, sçenekli denetim yüklemesi(.md5) için önce', UPLOAD_TEXT: 'Yeni bir bellenim(.bin) dosyası yükleyin, ayarlar ve özelleştirmeler(.json) dosyası aşağıda, sçenekli denetim yüklemesi(.md5) için önce',
UPLOADING: 'Yüklüyor',
UPLOAD_DROP_TEXT: 'Buraya tıklayın yada dosyayı sürükleyip bırakın', UPLOAD_DROP_TEXT: 'Buraya tıklayın yada dosyayı sürükleyip bırakın',
ERROR: 'Beklenemedik hata, lütfen tekrar deneyin.', ERROR: 'Beklenemedik hata, lütfen tekrar deneyin.',
TIME_SET: 'Zaman ayarı', TIME_SET: 'Zaman ayarı',
@@ -325,7 +322,7 @@ const tr: Translation = {
SYSTEM_MEMORY: 'System Memory', // TODO translate SYSTEM_MEMORY: 'System Memory', // TODO translate
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
MODULES: 'Module', // TODO translate MODULES: 'Module', // TODO translate
MODULES_1: 'Harici modülleri etkinleştirin veya devre dışı bırakın', // TODO translate MODULES_1: 'Harici modülleri etkinleştirin veya devre dışı bırakın', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate MODULES_UPDATED: 'Modules updated', // TODO translate
@@ -333,7 +330,10 @@ const tr: Translation = {
MODULES_NONE: 'No external modules detected', // TODO translate MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename', // TODO translate RENAME: 'Rename', // TODO translate
ENABLE_MODBUS: 'Enable Modbus', // TODO translate ENABLE_MODBUS: 'Enable Modbus', // TODO translate
VIEW_LOG: 'View log to diagnose issues' // TODO translate VIEW_LOG: 'View log to diagnose issues', // TODO translate
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
SERVICES: 'Services', // TODO translate
ALLVALUES: 'All Values' // TODO translate
}; };
export default tr; export default tr;

View File

@@ -2,51 +2,42 @@ import { useState } from 'react';
import { useBlocker } from 'react-router-dom'; import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { type Method, useRequest } from 'alova'; import type { AlovaGenerics, Method } from 'alova';
import { useRequest } from 'alova/client';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
export interface RestRequestOptions<D> { export interface RestRequestOptions<D> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any read: () => Method<AlovaGenerics>;
read: () => Method<any, any, any, any, any, any, any>; update: (value: D) => Method<AlovaGenerics>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
update: (value: D) => Method<any, any, any, any, any, any, any>;
} }
export const useRest = <D>({ read, update }: RestRequestOptions<D>) => { export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [errorMessage, setErrorMessage] = useState<string>(); const [errorMessage, setErrorMessage] = useState<string>();
const [restartNeeded, setRestartNeeded] = useState<boolean>(false); const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
const [origData, setOrigData] = useState<D>(); const [origData, setOrigData] = useState<D>();
const [dirtyFlags, setDirtyFlags] = useState<string[]>([]); const [dirtyFlags, setDirtyFlags] = useState<string[]>([]);
const blocker = useBlocker(dirtyFlags.length !== 0); const blocker = useBlocker(dirtyFlags.length !== 0);
const { const {
data, data,
send: readData, send: readData,
update: updateData, update: updateData
onComplete: onReadComplete } = useRequest(read()).onComplete((event) => {
} = useRequest(read()); setOrigData(event.data as D);
});
const { const { loading: saving, send: writeData } = useRequest(
loading: saving, (newData: D) => update(newData),
send: writeData, { immediate: false }
onSuccess: onWriteSuccess ).onSuccess(() => {
} = useRequest((newData: D) => update(newData), { immediate: false });
const updateDataValue = (new_data: D) => {
updateData({ data: new_data });
};
onWriteSuccess(() => {
toast.success(LL.UPDATED_OF(LL.SETTINGS(1))); toast.success(LL.UPDATED_OF(LL.SETTINGS(1)));
setDirtyFlags([]); setDirtyFlags([]);
}); });
onReadComplete((event) => { const updateDataValue = (new_data: D) => {
setOrigData(event.data as D); updateData({ data: new_data });
}); };
const loadData = async () => { const loadData = async () => {
setDirtyFlags([]); setDirtyFlags([]);
@@ -74,7 +65,6 @@ export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
} }
}); });
}; };
return { return {
loadData, loadData,
saveData, saveData,

View File

@@ -12,7 +12,7 @@ export const validate = <T extends object>(
options ? options : {}, options ? options : {},
(errors, fieldErrors) => { (errors, fieldErrors) => {
if (errors) { if (errors) {
reject(fieldErrors); reject(fieldErrors as Error);
} else { } else {
resolve(source as T); resolve(source as T);
} }

View File

@@ -4,11 +4,13 @@ import { defineConfig } from 'vite';
import viteImagemin from 'vite-plugin-imagemin'; import viteImagemin from 'vite-plugin-imagemin';
import viteTsconfigPaths from 'vite-tsconfig-paths'; import viteTsconfigPaths from 'vite-tsconfig-paths';
import mockServer from '../mock-api/mockServer.js';
export default defineConfig(({ command, mode }) => { export default defineConfig(({ command, mode }) => {
if (command === 'serve') { if (command === 'serve') {
console.log('Preparing for standalone build with server, mode=' + mode); console.log('Preparing for standalone build with server, mode=' + mode);
return { return {
plugins: [preact(), viteTsconfigPaths()], plugins: [preact(), viteTsconfigPaths(), mockServer()],
server: { server: {
open: true, open: true,
port: mode == 'production' ? 4173 : 3000, port: mode == 'production' ? 4173 : 3000,
@@ -18,12 +20,6 @@ export default defineConfig(({ command, mode }) => {
changeOrigin: true, changeOrigin: true,
secure: false secure: false
}, },
'/es': {
target: 'http://localhost:3081',
changeOrigin: true,
secure: false
},
'/rest/uploadFile': 'http://localhost:3082', // this must come first to work!
'/rest': 'http://localhost:3080' '/rest': 'http://localhost:3080'
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -75,6 +75,7 @@ void APSettingsService::manageAP() {
} }
void APSettingsService::startAP() { void APSettingsService::startAP() {
WiFi.softAPenableIpV6(); // force IPV6, same as for WiFi - fixes https://github.com/emsesp/EMS-ESP32/issues/1922
WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask); WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask);
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_AP), WIFI_BW_HT20); esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_AP), WIFI_BW_HT20);
WiFi.softAP(_state.ssid.c_str(), _state.password.c_str(), _state.channel, _state.ssidHidden, _state.maxClients); WiFi.softAP(_state.ssid.c_str(), _state.password.c_str(), _state.channel, _state.ssidHidden, _state.maxClients);

View File

@@ -1,69 +0,0 @@
import express from 'express';
const rest_server = express();
const port = 3081;
const ES_ENDPOINT_ROOT = '/es/';
const ES_LOG_ENDPOINT = ES_ENDPOINT_ROOT + 'log';
const INTERVAL = 1000;
function pad(number) {
var r = String(number);
if (r.length === 1) {
r = '0' + r;
}
return r;
}
// e.g. 2024-03-29 07:02:37.856
Date.prototype.toISOString = function () {
return (
this.getUTCFullYear() +
'-' +
pad(this.getUTCMonth() + 1) +
'-' +
pad(this.getUTCDate()) +
' ' +
pad(this.getUTCHours()) +
':' +
pad(this.getUTCMinutes()) +
':' +
pad(this.getUTCSeconds()) +
'.' +
String((this.getUTCMilliseconds() / 1000).toFixed(3)).slice(2, 5)
);
};
rest_server.get(ES_LOG_ENDPOINT, (_req, res) => {
res.writeHead(200, {
Connection: 'keep-alive',
'Cache-Control': 'no-cache',
'Content-Type': 'text/event-stream'
});
let count = 0;
const interval = setInterval(() => {
const data = {
t: new Date().toISOString(),
l: (3 + (count % 6)),
i: count,
n: 'system',
m: 'message #' + count++
};
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, INTERVAL);
// if client closes connection
res.on('close', () => {
console.log('Closing ES connection');
clearInterval(interval);
res.end();
});
});
// start eventsource server
rest_server.listen(port, () => console.log(`EMS-ESP EventSource mock server running on http://localhost:${port}/`));

123
mock-api/mockServer.js Normal file
View File

@@ -0,0 +1,123 @@
import formidable from 'formidable';
function pad(number) {
var r = String(number);
if (r.length === 1) {
r = '0' + r;
}
return r;
}
// e.g. 2024-03-29 07:02:37.856
Date.prototype.toISOString = function () {
return (
this.getUTCFullYear() +
'-' +
pad(this.getUTCMonth() + 1) +
'-' +
pad(this.getUTCDate()) +
' ' +
pad(this.getUTCHours()) +
':' +
pad(this.getUTCMinutes()) +
':' +
pad(this.getUTCSeconds()) +
'.' +
String((this.getUTCMilliseconds() / 1000).toFixed(3)).slice(2, 5)
);
};
export default () => {
return {
name: 'vite:mockserver',
configureServer: async (server) => {
server.middlewares.use(async (req, res, next) => {
// catch any file uploads
if (req.url.startsWith('/rest/uploadFile')) {
let progress = 0;
const file_size = req.headers['content-length'];
req.on('data', async (chunk) => {
progress += chunk.length;
const percentage = (progress / file_size) * 100;
console.log(`Progress: ${Math.round(percentage)}%`);
// await new Promise((resolve) => setTimeout(() => resolve(), 3000)); // slow it down
});
const form = formidable({});
let fields;
let files;
try {
[fields, files] = await form.parse(req);
} catch (err) {
console.error(err);
res.writeHead(err.httpCode || 400, {
'Content-Type': 'text/plain'
});
res.end(String(err));
return;
}
// only process when we have a file
if (Object.keys(files).length > 0) {
const uploaded_file = files.file[0];
const file_name = uploaded_file.originalFilename;
const file_extension = file_name.substring(
file_name.lastIndexOf('.') + 1
);
console.log('Filename: ' + file_name);
console.log('Extension: ' + file_extension);
console.log('File size: ' + file_size);
if (file_extension === 'bin' || file_extension === 'json') {
console.log('File uploaded successfully!');
} else if (file_extension === 'md5') {
console.log('MD5 hash generated successfully!');
res.end(
JSON.stringify({
md5: 'ef4304fc4d9025a58dcf25d71c882d2c'
})
);
} else {
res.statusCode = 400;
console.log('Invalid file extension!');
}
}
res.end();
}
// SSE Eventsource
else if (req.url.startsWith('/es/log')) {
res.writeHead(200, {
Connection: 'keep-alive',
'Cache-Control': 'no-cache',
'Content-Type': 'text/event-stream'
});
let count = 0;
const interval = setInterval(() => {
const data = {
t: new Date().toISOString(),
l: 3 + (count % 6),
i: count,
n: 'system',
m: 'message #' + count++
};
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 1000);
// if client closes connection
res.on('close', () => {
console.log('Closing ES connection');
clearInterval(interval);
res.end();
});
} else {
next(); // move on to the next middleware function in chain
}
});
}
};
};

View File

@@ -6,15 +6,16 @@
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"mock-rest": "bun --watch rest_server.ts", "mock-rest": "bun --watch rest_server.ts",
"mock-es": "bun --watch es_server.ts", "format": "prettier -l -w '**/*.{ts,tsx,js,css,json,md}'"
"mock-upload": "bun --watch upload_server.ts"
}, },
"dependencies": { "dependencies": {
"@msgpack/msgpack": "^2.8.0", "@msgpack/msgpack": "^2.8.0",
"@types/multer": "^1.4.11", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"express": "^4.19.2", "eslint": "^9.9.0",
"eslint-config-prettier": "^9.1.0",
"formidable": "^3.5.1",
"itty-router": "^5.0.17", "itty-router": "^5.0.17",
"multer": "^1.4.5-lts.1" "prettier": "^3.3.3"
}, },
"packageManager": "yarn@4.2.1" "packageManager": "yarn@4.4.0"
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,55 +0,0 @@
import express from 'express';
import multer from 'multer';
const rest_server = express();
const port = 3082;
const upload = multer({ dest: './uploads' });
const UPLOAD_FILE_ENDPOINT = '/rest/uploadFile';
// delay functions, 2 different types
const delay = (ms) => new Promise((res) => setTimeout(res, ms));
function delay_blocking(milliseconds) {
var start = new Date().getTime();
while (true) {
if (new Date().getTime() - start > milliseconds) {
break;
}
}
}
function progress_middleware(req, _res, next) {
let progress = 0;
const file_size = req.headers['content-length'];
console.log('Uploading file. Size ' + file_size + ' bytes');
// set event listener
req.on('data', async (chunk) => {
progress += chunk.length;
const percentage = (progress / file_size) * 100;
console.log(`Progress: ${Math.round(percentage)}%`);
// await delay(1000); // slow it down
delay_blocking(1000); // slow it down
});
next(); // invoke next middleware which is multer
}
rest_server.post(UPLOAD_FILE_ENDPOINT, progress_middleware, upload.single('file'), (req, res) => {
if (req.file) {
const filename = req.file.originalname;
const ext = filename.substring(filename.lastIndexOf('.') + 1);
console.log(req.file);
if (ext === 'bin' || ext === 'json') {
console.log('Received firmware or json file, extension: ' + ext);
return res.sendStatus(200);
} else if (ext === 'md5') {
return res.json({ md5: 'ef4304fc4d9025a58dcf25d71c882d2c' });
}
}
console.log('Invalid file extension');
return res.sendStatus(400);
});
// start server
rest_server.listen(port, () => console.log(`EMS-ESP File Upload mock server running on http://localhost:${port}/`));

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@
; default_envs = esp32_4M ; default_envs = esp32_4M
; default_envs = esp32_16M ; default_envs = esp32_16M
default_envs = lolin_s3 default_envs = lolin_s3
; default_envs = lolin_c3_mini
; default_envs = native ; default_envs = native
; default_envs = debug ; default_envs = debug
; default_envs = custom ; default_envs = custom
@@ -26,11 +27,11 @@ default_envs = lolin_s3
[env] [env]
; upload settings ; upload settings
; for USB ; for USB
upload_protocol = esptool ; upload_protocol = esptool
upload_port = /dev/ttyUSB* ; upload_port = /dev/ttyUSB*
; for OTA add scripts/upload.py to extra_scripts ; for OTA add scripts/upload.py to extra_scripts
; upload_protocol = custom upload_protocol = custom
custom_emsesp_ip = 10.10.10.173 custom_emsesp_ip = 10.10.10.175
; custom_emsesp_ip = ems-esp.local ; custom_emsesp_ip = ems-esp.local
custom_username = admin custom_username = admin
custom_password = admin custom_password = admin
@@ -42,59 +43,25 @@ custom_password = admin
[env:native] [env:native]
extra_scripts = extra_scripts =
; pre:scripts/refresh_module_library_native.py ; pre:scripts/refresh_module_library_native.py
; post:scripts/run_native.py post:scripts/run_native.py
[env:esp32_4M]
extra_scripts =
; pre:scripts/build_interface.py ; comment out if you don't want to re-build the WebUI each time
scripts/rename_fw.py
; scripts/upload.py
[env:lolin_s3] [env:lolin_s3]
extra_scripts = extra_scripts =
; pre:scripts/build_interface.py ; comment out if you don't want to re-build the WebUI each time pre:scripts/build_interface.py ; comment out if you don't want to re-build the WebUI each time
scripts/rename_fw.py scripts/rename_fw.py
; scripts/upload.py scripts/upload.py
[env:esp32_16M] [env:esp32_16M]
extra_scripts = extra_scripts =
; pre:scripts/build_interface.py ; comment out if you don't want to re-build the WebUI each time pre:scripts/build_interface.py ; comment out if you don't want to re-build the WebUI each time
scripts/rename_fw.py scripts/rename_fw.py
; scripts/upload.py ; scripts/upload.py
[env:custom] [env:lolin_c3_mini]
; use for basic ESP boards with 4MB flash
; make sure -D TASMOTA_SDK is also enabled
platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.01.00/platform-espressif32.zip
; use for S3 boards:
; platform = espressif32
framework = arduino
board = esp32dev
board_build.filesystem = littlefs
board_build.f_cpu = 240000000L
board_upload.flash_size = 4MB
board_build.partitions = esp32_partition_4M.csv
board_upload.use_1200bps_touch = false
board_upload.wait_for_upload_port = true
upload_port = /dev/ttyUSB0
extra_scripts = extra_scripts =
pre:scripts/build_interface.py ; pre:scripts/build_interface.py ; comment out if you don't want to re-build the WebUI each time
scripts/rename_fw.py scripts/rename_fw.py
build_unflags = ${common.unbuild_flags} scripts/upload.py
build_flags =
${common.core_build_flags}
${factory_settings.build_flags}
${common.my_build_flags}
-D ONEWIRE_CRC16=0
-D NO_GLOBAL_ARDUINOOTA
-D ARDUINOJSON_ENABLE_STD_STRING=1
-D TASMOTA_SDK
; -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_WARN
-D EMSESP_TEST
-D EMSESP_DEBUG
-D CONFIG_ETH_ENABLED
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192
'-DEMSESP_DEFAULT_BOARD_PROFILE="Test"'
; pio run -e debug ; pio run -e debug
; or from Visual Studio Code do PIO -> Project Tasks -> debug -> General -> Upload and Monitor ; or from Visual Studio Code do PIO -> Project Tasks -> debug -> General -> Upload and Monitor

View File

@@ -215,6 +215,8 @@ build_unflags = ${common.unbuild_flags}
; ;
; It will generate an executbale which when run will show the EMS-ESP Console where you can run tests using the `test` command. ; It will generate an executbale which when run will show the EMS-ESP Console where you can run tests using the `test` command.
; ;
; See https://docs.platformio.org/en/latest/core/installation/shell-commands.html#piocore-install-shell-commands
;
; to build and run directly on linux: pio run -e native -t exec ; to build and run directly on linux: pio run -e native -t exec
; ;
; to build and run on Windows, it needs winsock for the console input so: ; to build and run on Windows, it needs winsock for the console input so:
@@ -245,7 +247,6 @@ build_src_flags =
-I./lib/espMqttClient/src/Transport -I./lib/espMqttClient/src/Transport
build_src_filter = build_src_filter =
+<*> +<*>
-<.git/>
+<../lib_standalone> +<../lib_standalone>
+<../lib/uuid-common> +<../lib/uuid-common>
+<../lib/uuid-console> +<../lib/uuid-console>

View File

@@ -1,11 +1,60 @@
import shutil
import re
import os import os
Import("env") Import("env")
def run_native(): OUTPUT_DIR = "build{}".format(os.path.sep)
print("Running native...")
os.system("pio run -e native")
if not (env.IsCleanTarget()): def move_file(source, target, env):
run_native()
# get the build info
bag = {}
exprs = [(re.compile(r'^#define EMSESP_APP_VERSION\s+"(\S+)"'), 'app_version')]
with open('./src/version.h', 'r') as f:
for l in f.readlines():
for expr, var in exprs:
m = expr.match(l)
if m and len(m.groups()) > 0:
bag[var] = m.group(1)
app_version = bag.get('app_version')
platform = "native"
# this breaks the CI so removed
# flash_size = env["PIOENV"].split('_')[1]
# print(env.Dump())
# my_flags = env.ParseFlags(env['BUILD_FLAGS'])
# defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")}
# print(my_flags)
# print(my_flags.get("CPPDEFINES")
# alternatively take platform from the pio target
# platform = str(target[0]).split(os.path.sep)[2]
print("app version: " + app_version)
print("platform: " + platform)
# TODO do I need .exe for windows?
variant = "native"
# check if output directories exist and create if necessary
if not os.path.isdir(OUTPUT_DIR):
os.mkdir(OUTPUT_DIR)
# create string with location and file names based on variant
bin_file = "{}firmware{}{}".format(OUTPUT_DIR, os.path.sep, variant)
# check if new target files exist and remove if necessary
for f in [bin_file]:
if os.path.isfile(f):
os.remove(f)
print("Renaming file to "+bin_file)
shutil.copy(str(target[0]), bin_file)
print("Executing file")
os.system(bin_file)
env.AddPostAction("$BUILD_DIR/${PROGNAME}", [move_file])

View File

@@ -418,7 +418,7 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
LOG_WARNING(error); LOG_WARNING(error);
} else { } else {
if (single_command) { if (single_command) {
// log as DEBUG (TRACE) regarless if compiled with EMSESP_DEBUG // log as DEBUG (TRACE) regardless if compiled with EMSESP_DEBUG
logger_.debug(("%sCalled command %s"), ro.c_str(), info_s); logger_.debug(("%sCalled command %s"), ro.c_str(), info_s);
} else { } else {
if (id > 0) { if (id > 0) {

View File

@@ -691,11 +691,16 @@ std::string EMSESPShell::context_text() {
// when in su (admin) show # as the prompt suffix // when in su (admin) show # as the prompt suffix
std::string EMSESPShell::prompt_suffix() { std::string EMSESPShell::prompt_suffix() {
#ifndef EMSESP_UNITY
if (has_flags(CommandFlags::ADMIN)) { if (has_flags(CommandFlags::ADMIN)) {
return std::string{'#'}; return std::string{'#'};
} else { } else {
return std::string{'$'}; return std::string{'$'};
} }
#else
// don't bother with prompt suffix if we're testing Unity output
return "";
#endif
} }
void EMSESPShell::end_of_transmission() { void EMSESPShell::end_of_transmission() {

View File

@@ -1553,8 +1553,6 @@ void EMSESP::start() {
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
shell_->add_flags(CommandFlags::ADMIN); // always start in su/admin mode when running tests shell_->add_flags(CommandFlags::ADMIN); // always start in su/admin mode when running tests
#endif #endif
#else
#warning "Shell is disabled when running Unity tests."
#endif #endif
// start the file system // start the file system

View File

@@ -1398,7 +1398,12 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
// System // System
node = output["system"].to<JsonObject>(); node = output["system"].to<JsonObject>();
// prevent false negataive in Unity tests everytime the version changes
#if defined(EMSESP_UNITY)
node["version"] = "dev";
#else
node["version"] = EMSESP_APP_VERSION; node["version"] = EMSESP_APP_VERSION;
#endif
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3); node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
node["uptimeSec"] = uuid::get_uptime_sec(); node["uptimeSec"] = uuid::get_uptime_sec();
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE

View File

@@ -1109,6 +1109,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
Serial.println(); Serial.println();
Serial.printf("%s**** Testing bad urls ****\n%s", COLOR_RED, COLOR_RESET); Serial.printf("%s**** Testing bad urls ****\n%s", COLOR_RED, COLOR_RESET);
request.method(HTTP_GET);
request.url("/api/boiler2"); request.url("/api/boiler2");
EMSESP::webAPIService.webAPIService(&request); EMSESP::webAPIService.webAPIService(&request);

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.7.0-dev.30" #define EMSESP_APP_VERSION "3.7.0-dev.31"

View File

@@ -134,7 +134,7 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
const char * api_data = output["api_data"]; const char * api_data = output["api_data"];
if (api_data) { if (api_data) {
request->send(200, "text/plain; charset=utf-8", api_data); request->send(200, "text/plain; charset=utf-8", api_data);
#if defined(EMSESP_TEST) #if defined(EMSESP_UNITY)
// store the result so we can test with Unity later // store the result so we can test with Unity later
storeResponse(output); storeResponse(output);
#endif #endif
@@ -161,7 +161,7 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
request->send(response); request->send(response);
api_count_++; api_count_++;
#if defined(EMSESP_TEST) #if defined(EMSESP_UNITY)
// store the result so we can test with Unity later // store the result so we can test with Unity later
storeResponse(output); storeResponse(output);
#endif #endif
@@ -229,7 +229,7 @@ void WebAPIService::getEntities(AsyncWebServerRequest * request) {
request->send(response); request->send(response);
} }
#if defined(EMSESP_TEST) #if defined(EMSESP_UNITY)
// store the result so we can test with Unity later // store the result so we can test with Unity later
static JsonDocument storeResponseDoc_; static JsonDocument storeResponseDoc_;

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