mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 15:59:52 +03:00
@@ -21,6 +21,7 @@ export default tseslint.config(
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-unsafe-enum-comparison': 'off',
|
||||
'@typescript-eslint/no-unused-expressions': 'off',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||
'@typescript-eslint/no-misused-promises': [
|
||||
'error',
|
||||
|
||||
@@ -8,42 +8,33 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"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\"",
|
||||
"mock-rest": "bun --watch ../mock-api/rest_server.ts",
|
||||
"mock-es": "bun --watch ../mock-api/es_server.ts",
|
||||
"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\"",
|
||||
"standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"npm:mock-rest\" \"vite\"",
|
||||
"typesafe-i18n": "typesafe-i18n --no-watch",
|
||||
"webUI": "node progmem-generator.js",
|
||||
"format": "prettier -l -w '**/*.{ts,tsx,js,css,json,md}'",
|
||||
"lint": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alova/adapter-xhr": "^1.0.6",
|
||||
"@alova/scene-react": "^1.6.2",
|
||||
"@alova/adapter-xhr": "2.0.4",
|
||||
"@emotion/react": "^11.13.0",
|
||||
"@emotion/styled": "^11.13.0",
|
||||
"@mui/icons-material": "^5.16.6",
|
||||
"@mui/material": "^5.16.6",
|
||||
"@mui/icons-material": "^5.16.7",
|
||||
"@mui/material": "^5.16.7",
|
||||
"@table-library/react-table-library": "4.1.7",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@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",
|
||||
"alova": "3.0.9",
|
||||
"async-validator": "^4.2.5",
|
||||
"history": "^5.3.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mime-types": "^2.1.35",
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-icons": "^5.2.1",
|
||||
"preact": "^10.23.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-router-dom": "^6.26.0",
|
||||
"react-toastify": "^10.0.5",
|
||||
"typesafe-i18n": "^5.26.2",
|
||||
@@ -51,22 +42,27 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@eslint/js": "^9.8.0",
|
||||
"@eslint/js": "^9.9.0",
|
||||
"@preact/compat": "^17.1.2",
|
||||
"@preact/preset-vite": "^2.9.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@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",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint": "^9.9.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"preact": "^10.23.1",
|
||||
"formidable": "^3.5.1",
|
||||
"prettier": "^3.3.3",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"terser": "^5.31.3",
|
||||
"typescript-eslint": "8.0.0",
|
||||
"vite": "^5.3.5",
|
||||
"terser": "^5.31.6",
|
||||
"typescript-eslint": "8.1.0",
|
||||
"vite": "^5.4.1",
|
||||
"vite-plugin-imagemin": "^0.6.1",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
"vite-tsconfig-paths": "^5.0.1"
|
||||
},
|
||||
"packageManager": "yarn@4.4.0"
|
||||
}
|
||||
|
||||
@@ -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/
|
||||
* Download woff2 using e.g. https://fonts.googleapis.com/css2?family=Lato or https://fonts.googleapis.com/css2?family=Roboto
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { Slide, ToastContainer } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.min.css';
|
||||
|
||||
@@ -12,7 +11,7 @@ import { localStorageDetector } from 'typesafe-i18n/detectors';
|
||||
|
||||
const detectedLocale = detectLocale(localStorageDetector);
|
||||
|
||||
const App: FC = () => {
|
||||
const App = () => {
|
||||
const [wasLoaded, setWasLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useContext, useEffect } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AuthenticatedRouting from 'AuthenticatedRouting';
|
||||
@@ -14,7 +13,7 @@ interface SecurityRedirectProps {
|
||||
signOut?: boolean;
|
||||
}
|
||||
|
||||
const RootRedirect: FC<SecurityRedirectProps> = ({ message, signOut }) => {
|
||||
const RootRedirect = ({ message, signOut }: SecurityRedirectProps) => {
|
||||
const authenticationContext = useContext(AuthenticationContext);
|
||||
useEffect(() => {
|
||||
signOut && authenticationContext.signOut(false);
|
||||
@@ -23,26 +22,11 @@ const RootRedirect: FC<SecurityRedirectProps> = ({ message, signOut }) => {
|
||||
return <Navigate to="/" />;
|
||||
};
|
||||
|
||||
export const RemoveTrailingSlashes = () => {
|
||||
const location = useLocation();
|
||||
return (
|
||||
location.pathname.match('/.*/$') && (
|
||||
<Navigate
|
||||
to={{
|
||||
pathname: location.pathname.replace(/\/+$/, ''),
|
||||
search: location.search
|
||||
}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const AppRouting: FC = () => {
|
||||
const AppRouting = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
return (
|
||||
<Authentication>
|
||||
<RemoveTrailingSlashes />
|
||||
<Routes>
|
||||
<Route
|
||||
path="/unauthorized"
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { type FC, useContext } from 'react';
|
||||
import { useContext } from 'react';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
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 Help from 'app/main/Help';
|
||||
import Modules from 'app/main/Modules';
|
||||
import Scheduler from 'app/main/Scheduler';
|
||||
import Sensors from 'app/main/Sensors';
|
||||
import APSettings from 'app/settings/APSettings';
|
||||
import ApplicationSettings from 'app/settings/ApplicationSettings';
|
||||
import DownloadUpload from 'app/settings/DownloadUpload';
|
||||
import MqttSettings from 'app/settings/MqttSettings';
|
||||
import NTPSettings from 'app/settings/NTPSettings';
|
||||
import Settings from 'app/settings/Settings';
|
||||
import UploadDownload from 'app/settings/UploadDownload';
|
||||
import Network from 'app/settings/network/Network';
|
||||
import Security from 'app/settings/security/Security';
|
||||
import APStatus from 'app/status/APStatus';
|
||||
@@ -26,9 +27,7 @@ import SystemLog from 'app/status/SystemLog';
|
||||
import { Layout } from 'components';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
|
||||
import Help from './app/main/Help';
|
||||
|
||||
const AuthenticatedRouting: FC = () => {
|
||||
const AuthenticatedRouting = () => {
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
return (
|
||||
<Layout>
|
||||
@@ -55,12 +54,12 @@ const AuthenticatedRouting: FC = () => {
|
||||
<Route path="/settings/ntp" element={<NTPSettings />} />
|
||||
<Route path="/settings/ap" element={<APSettings />} />
|
||||
<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/security/*" element={<Security />} />
|
||||
|
||||
<Route path="/customizations" element={<Customization />} />
|
||||
<Route path="/customizations" element={<Customizations />} />
|
||||
<Route path="/scheduler" element={<Scheduler />} />
|
||||
<Route path="/customentities" element={<CustomEntities />} />
|
||||
</>
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { useContext, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import ForwardIcon from '@mui/icons-material/Forward';
|
||||
import { Box, Button, Paper, Typography } from '@mui/material';
|
||||
|
||||
import * as AuthenticationApi from 'api/authentication';
|
||||
import { PROJECT_NAME } from 'api/env';
|
||||
|
||||
import { useRequest } from 'alova';
|
||||
import * as AuthenticationApi from 'components/routing/authentication';
|
||||
import { useRequest } from 'alova/client';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import {
|
||||
LanguageSelector,
|
||||
@@ -16,12 +13,13 @@ import {
|
||||
ValidatedTextField
|
||||
} from 'components';
|
||||
import { AuthenticationContext } from 'contexts/authentication';
|
||||
import { PROJECT_NAME } from 'env';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { SignInRequest } from 'types';
|
||||
import { onEnterCallback, updateValue } from 'utils';
|
||||
import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators';
|
||||
|
||||
const SignIn: FC = () => {
|
||||
const SignIn = () => {
|
||||
const authenticationContext = useContext(AuthenticationContext);
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
@@ -33,14 +31,12 @@ const SignIn: FC = () => {
|
||||
const [processing, setProcessing] = useState<boolean>(false);
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
|
||||
const { send: callSignIn, onSuccess } = useRequest(
|
||||
const { send: callSignIn } = useRequest(
|
||||
(request: SignInRequest) => AuthenticationApi.signIn(request),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
|
||||
onSuccess((response) => {
|
||||
).onSuccess((response) => {
|
||||
if (response.data) {
|
||||
authenticationContext.signIn(response.data.access_token);
|
||||
}
|
||||
@@ -113,6 +109,10 @@ const SignIn: FC = () => {
|
||||
onChange={updateLoginRequestValue}
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
inputProps={{
|
||||
autoCapitalize: 'none',
|
||||
autoCorrect: 'off'
|
||||
}}
|
||||
/>
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
|
||||
@@ -17,7 +17,7 @@ import type {
|
||||
Settings,
|
||||
WriteAnalogSensor,
|
||||
WriteTemperatureSensor
|
||||
} from './types';
|
||||
} from '../app/main/types';
|
||||
|
||||
// DashboardDevices
|
||||
export const readCoreData = () => alovaInstance.Get<CoreData>(`/rest/coreData`);
|
||||
@@ -65,7 +65,7 @@ export const readDeviceEntities = (id: number) =>
|
||||
alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, {
|
||||
params: { id },
|
||||
responseType: 'arraybuffer',
|
||||
transformData(data) {
|
||||
transform(data) {
|
||||
return (data as DeviceEntity[]).map((de: DeviceEntity) => ({
|
||||
...de,
|
||||
o_m: de.m,
|
||||
@@ -88,8 +88,7 @@ export const writeDeviceName = (data: { id: number; name: string }) =>
|
||||
// SettingsScheduler
|
||||
export const readSchedule = () =>
|
||||
alovaInstance.Get<ScheduleItem[]>('/rest/schedule', {
|
||||
name: 'schedule',
|
||||
transformData(data) {
|
||||
transform(data) {
|
||||
return (data as Schedule).schedule.map((si: ScheduleItem) => ({
|
||||
...si,
|
||||
o_id: si.id,
|
||||
@@ -109,8 +108,7 @@ export const writeSchedule = (data: Schedule) =>
|
||||
// Modules
|
||||
export const readModules = () =>
|
||||
alovaInstance.Get<ModuleItem[]>('/rest/modules', {
|
||||
name: 'modules',
|
||||
transformData(data) {
|
||||
transform(data) {
|
||||
return (data as Modules).modules.map((mi: ModuleItem) => ({
|
||||
...mi,
|
||||
o_enabled: mi.enabled,
|
||||
@@ -127,8 +125,7 @@ export const writeModules = (data: {
|
||||
// SettingsEntities
|
||||
export const readCustomEntities = () =>
|
||||
alovaInstance.Get<EntityItem[]>('/rest/customEntities', {
|
||||
name: 'entities',
|
||||
transformData(data) {
|
||||
transform(data) {
|
||||
return (data as Entities).entities.map((ei: EntityItem) => ({
|
||||
...ei,
|
||||
o_id: ei.id,
|
||||
@@ -1,19 +1,19 @@
|
||||
import { xhrRequestAdapter } from '@alova/adapter-xhr';
|
||||
import { type AlovaXHRResponse, xhrRequestAdapter } from '@alova/adapter-xhr';
|
||||
import { createAlova } from 'alova';
|
||||
import ReactHook from 'alova/react';
|
||||
|
||||
import { unpack } from '../api/unpack';
|
||||
import { unpack } from './unpack';
|
||||
|
||||
export const ACCESS_TOKEN = 'access_token';
|
||||
|
||||
export const alovaInstance = createAlova({
|
||||
statesHook: ReactHook,
|
||||
timeout: 3000, // 3 seconds but throwing a timeout error
|
||||
localCache: null,
|
||||
// localCache: {
|
||||
timeout: 3000, // 3 seconds before throwing a timeout error
|
||||
cacheFor: null, // disable cache
|
||||
// cacheFor: {
|
||||
// GET: {
|
||||
// mode: 'placeholder', // see https://alova.js.org/learning/response-cache/#cache-replaceholder-mode
|
||||
// expire: 2000
|
||||
// mode: 'memory',
|
||||
// expire: 60 * 10 * 1000 // 60 seconds in cache
|
||||
// }
|
||||
// },
|
||||
requestAdapter: xhrRequestAdapter(),
|
||||
@@ -22,10 +22,15 @@ export const alovaInstance = createAlova({
|
||||
method.config.headers.Authorization =
|
||||
'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: {
|
||||
onSuccess: async (response) => {
|
||||
onSuccess: async (response: AlovaXHRResponse) => {
|
||||
// if (response.status === 202) {
|
||||
// throw new Error('Wait'); // wifi scan in progress
|
||||
// } else
|
||||
|
||||
@@ -7,12 +7,9 @@ export const readNetworkStatus = () =>
|
||||
export const scanNetworks = () => alovaInstance.Get('/rest/scanNetworks');
|
||||
export const listNetworks = () =>
|
||||
alovaInstance.Get<WiFiNetworkList>('/rest/listNetworks', {
|
||||
name: 'listNetworks',
|
||||
timeout: 20000 // timeout 20 seconds
|
||||
});
|
||||
export const readNetworkSettings = () =>
|
||||
alovaInstance.Get<NetworkSettingsType>('/rest/networkSettings', {
|
||||
name: 'networkSettings'
|
||||
});
|
||||
alovaInstance.Get<NetworkSettingsType>('/rest/networkSettings');
|
||||
export const updateNetworkSettings = (wifiSettings: NetworkSettingsType) =>
|
||||
alovaInstance.Post<NetworkSettingsType>('/rest/networkSettings', wifiSettings);
|
||||
|
||||
@@ -4,10 +4,9 @@ import { alovaInstance } from './endpoints';
|
||||
|
||||
export const readNTPStatus = () =>
|
||||
alovaInstance.Get<NTPStatusType>('/rest/ntpStatus');
|
||||
|
||||
export const readNTPSettings = () =>
|
||||
alovaInstance.Get<NTPSettingsType>('/rest/ntpSettings', {
|
||||
name: 'ntpSettings'
|
||||
});
|
||||
alovaInstance.Get<NTPSettingsType>('/rest/ntpSettings', {});
|
||||
export const updateNTPSettings = (data: NTPSettingsType) =>
|
||||
alovaInstance.Post<NTPSettingsType>('/rest/ntpSettings', data);
|
||||
|
||||
|
||||
@@ -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 { alovaInstance, alovaInstanceGH } from './endpoints';
|
||||
@@ -31,13 +26,13 @@ export const fetchLogES = () => alovaInstance.Get('/es/log');
|
||||
// Get versions from github
|
||||
export const getStableVersion = () =>
|
||||
alovaInstanceGH.Get('latest', {
|
||||
transformData(response) {
|
||||
transform(response: { data: { name: string } }) {
|
||||
return response.data.name.substring(1);
|
||||
}
|
||||
});
|
||||
export const getDevVersion = () =>
|
||||
alovaInstanceGH.Get('tags/latest', {
|
||||
transformData(response) {
|
||||
transform(response: { data: { name: string } }) {
|
||||
return response.data.name.split(/\s+/).splice(-1)[0].substring(1);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useBlocker } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
|
||||
@@ -20,7 +18,7 @@ import {
|
||||
Table
|
||||
} from '@table-library/react-table-library/table';
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import { updateState, useRequest } from 'alova';
|
||||
import { updateState, useAutoRequest, useRequest } from 'alova/client';
|
||||
import {
|
||||
BlockNavigation,
|
||||
ButtonRow,
|
||||
@@ -30,13 +28,13 @@ import {
|
||||
} from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
import { readCustomEntities, writeCustomEntities } from '../../api/app';
|
||||
import SettingsCustomEntitiesDialog from './CustomEntitiesDialog';
|
||||
import { DeviceValueTypeNames, DeviceValueUOM_s } from './types';
|
||||
import type { Entities, EntityItem } from './types';
|
||||
import { entityItemValidation } from './validators';
|
||||
|
||||
const CustomEntities: FC = () => {
|
||||
const CustomEntities = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const [numChanges, setNumChanges] = useState<number>(0);
|
||||
const blocker = useBlocker(numChanges !== 0);
|
||||
@@ -50,13 +48,13 @@ const CustomEntities: FC = () => {
|
||||
data: entities,
|
||||
send: fetchEntities,
|
||||
error
|
||||
} = useRequest(EMSESP.readCustomEntities, {
|
||||
} = useAutoRequest(readCustomEntities, {
|
||||
initialData: [],
|
||||
force: true
|
||||
pollingTime: 2000
|
||||
});
|
||||
|
||||
const { send: writeEntities } = useRequest(
|
||||
(data: Entities) => EMSESP.writeCustomEntities(data),
|
||||
(data: Entities) => writeCustomEntities(data),
|
||||
{ immediate: false }
|
||||
);
|
||||
|
||||
@@ -182,8 +180,7 @@ const CustomEntities: FC = () => {
|
||||
|
||||
const onDialogSave = (updatedItem: EntityItem) => {
|
||||
setDialogOpen(false);
|
||||
|
||||
updateState('entities', (data: EntityItem[]) => {
|
||||
void updateState(readCustomEntities(), (data: EntityItem[]) => {
|
||||
const new_data = creating
|
||||
? [
|
||||
...data.filter((ei) => creating || ei.o_id !== updatedItem.o_id),
|
||||
@@ -327,15 +324,6 @@ const CustomEntities: FC = () => {
|
||||
)}
|
||||
</Box>
|
||||
<Box flexWrap="nowrap" whiteSpace="nowrap">
|
||||
<ButtonRow>
|
||||
<Button
|
||||
startIcon={<RefreshIcon />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={fetchEntities}
|
||||
>
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<AddIcon />}
|
||||
variant="outlined"
|
||||
@@ -344,7 +332,6 @@ const CustomEntities: FC = () => {
|
||||
>
|
||||
{LL.ADD(0)}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
</Box>
|
||||
</SectionContent>
|
||||
|
||||
@@ -291,7 +291,11 @@ const CustomEntitiesDialog = ({
|
||||
fullWidth
|
||||
margin="normal"
|
||||
type="number"
|
||||
inputProps={{ min: '1', max: String(256 - editItem.offset), step: '1' }}
|
||||
inputProps={{
|
||||
min: '1',
|
||||
max: String(256 - editItem.offset),
|
||||
step: '1'
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useBlocker, useLocation } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
@@ -27,7 +26,7 @@ import {
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
import { restart } from 'api/system';
|
||||
|
||||
import {
|
||||
Body,
|
||||
@@ -40,7 +39,7 @@ import {
|
||||
} from '@table-library/react-table-library/table';
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova';
|
||||
import { useRequest } from 'alova/client';
|
||||
import RestartMonitor from 'app/status/RestartMonitor';
|
||||
import {
|
||||
BlockNavigation,
|
||||
@@ -51,8 +50,14 @@ import {
|
||||
} from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
import SettingsCustomizationDialog from './CustomizationDialog';
|
||||
import {
|
||||
readDeviceEntities,
|
||||
readDevices,
|
||||
resetCustomizations,
|
||||
writeCustomizationEntities,
|
||||
writeDeviceName
|
||||
} from '../../api/app';
|
||||
import SettingsCustomizationsDialog from './CustomizationsDialog';
|
||||
import EntityMaskToggle from './EntityMaskToggle';
|
||||
import OptionIcon from './OptionIcon';
|
||||
import { DeviceEntityMask } from './types';
|
||||
@@ -60,7 +65,7 @@ import type { DeviceEntity, DeviceShort } from './types';
|
||||
|
||||
export const APIURL = window.location.origin + '/api/';
|
||||
|
||||
const Customization: FC = () => {
|
||||
const Customizations = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const [numChanges, setNumChanges] = useState<number>(0);
|
||||
const blocker = useBlocker(numChanges !== 0);
|
||||
@@ -78,7 +83,7 @@ const Customization: FC = () => {
|
||||
useLayoutTitle(LL.CUSTOMIZATIONS());
|
||||
|
||||
// fetch devices first
|
||||
const { data: devices, send: fetchDevices } = useRequest(EMSESP.readDevices);
|
||||
const { data: devices, send: fetchDevices } = useRequest(readDevices);
|
||||
|
||||
const [selectedDevice, setSelectedDevice] = useState<number>(
|
||||
Number(useLocation().state) || -1
|
||||
@@ -87,32 +92,33 @@ const Customization: FC = () => {
|
||||
useState<string>(''); // needed for API URL
|
||||
const [selectedDeviceName, setSelectedDeviceName] = useState<string>('');
|
||||
|
||||
const { send: resetCustomizations } = useRequest(EMSESP.resetCustomizations(), {
|
||||
const { send: sendResetCustomizations } = useRequest(resetCustomizations(), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const { send: writeDeviceName } = useRequest(
|
||||
(data: { id: number; name: string }) => EMSESP.writeDeviceName(data),
|
||||
const { send: sendDeviceName } = useRequest(
|
||||
(data: { id: number; name: string }) => writeDeviceName(data),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
|
||||
const { send: writeCustomizationEntities } = useRequest(
|
||||
(data: { id: number; entity_ids: string[] }) =>
|
||||
EMSESP.writeCustomizationEntities(data),
|
||||
const { send: sendCustomizationEntities } = useRequest(
|
||||
(data: { id: number; entity_ids: string[] }) => writeCustomizationEntities(data),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
|
||||
const { send: readDeviceEntities, onSuccess: onSuccess } = useRequest(
|
||||
(data: number) => EMSESP.readDeviceEntities(data),
|
||||
const { send: sendDeviceEntities } = useRequest(
|
||||
(data: number) => readDeviceEntities(data),
|
||||
{
|
||||
initialData: [],
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
).onSuccess((event) => {
|
||||
setOriginalSettings(event.data);
|
||||
});
|
||||
|
||||
const setOriginalSettings = (data: DeviceEntity[]) => {
|
||||
setDeviceEntities(
|
||||
@@ -126,11 +132,7 @@ const Customization: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
onSuccess((event) => {
|
||||
setOriginalSettings(event.data);
|
||||
});
|
||||
|
||||
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
||||
const { send: sendRestart } = useRequest(restart(), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
@@ -231,7 +233,7 @@ const Customization: FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (devices && selectedDevice !== -1) {
|
||||
void readDeviceEntities(selectedDevice);
|
||||
void sendDeviceEntities(selectedDevice);
|
||||
const id = devices.devices.findIndex((d) => d.i === selectedDevice);
|
||||
if (id === -1) {
|
||||
setSelectedDevice(-1);
|
||||
@@ -245,8 +247,8 @@ const Customization: FC = () => {
|
||||
}
|
||||
}, [devices, selectedDevice]);
|
||||
|
||||
const restart = async () => {
|
||||
await restartCommand().catch((error: Error) => {
|
||||
const doRestart = async () => {
|
||||
await sendRestart().catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
setRestarting(true);
|
||||
@@ -327,7 +329,7 @@ const Customization: FC = () => {
|
||||
|
||||
const resetCustomization = async () => {
|
||||
try {
|
||||
await resetCustomizations();
|
||||
await sendResetCustomizations();
|
||||
toast.info(LL.CUSTOMIZATIONS_RESTART());
|
||||
} catch (error) {
|
||||
toast.error((error as Error).message);
|
||||
@@ -387,7 +389,7 @@ const Customization: FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
await writeCustomizationEntities({
|
||||
await sendCustomizationEntities({
|
||||
id: selectedDevice,
|
||||
entity_ids: masked_entities
|
||||
}).catch((error: Error) => {
|
||||
@@ -402,7 +404,7 @@ const Customization: FC = () => {
|
||||
};
|
||||
|
||||
const renameDevice = async () => {
|
||||
await writeDeviceName({ id: selectedDevice, name: selectedDeviceName })
|
||||
await sendDeviceName({ id: selectedDevice, name: selectedDeviceName })
|
||||
.then(() => {
|
||||
toast.success(LL.UPDATED_OF(LL.NAME(1)));
|
||||
})
|
||||
@@ -676,7 +678,7 @@ const Customization: FC = () => {
|
||||
startIcon={<PowerSettingsNewIcon />}
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={restart}
|
||||
onClick={doRestart}
|
||||
>
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
@@ -691,7 +693,7 @@ const Customization: FC = () => {
|
||||
startIcon={<CancelIcon />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={() => devices && readDeviceEntities(selectedDevice)}
|
||||
onClick={() => devices && sendDeviceEntities(selectedDevice)}
|
||||
>
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
@@ -727,7 +729,7 @@ const Customization: FC = () => {
|
||||
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||
{restarting ? <RestartMonitor /> : renderContent()}
|
||||
{selectedDeviceEntity && (
|
||||
<SettingsCustomizationDialog
|
||||
<SettingsCustomizationsDialog
|
||||
open={dialogOpen}
|
||||
onClose={onDialogClose}
|
||||
onSave={onDialogSave}
|
||||
@@ -738,4 +740,4 @@ const Customization: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Customization;
|
||||
export default Customizations;
|
||||
@@ -23,19 +23,19 @@ import EntityMaskToggle from './EntityMaskToggle';
|
||||
import { DeviceEntityMask } from './types';
|
||||
import type { DeviceEntity } from './types';
|
||||
|
||||
interface SettingsCustomizationDialogProps {
|
||||
interface SettingsCustomizationsDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (di: DeviceEntity) => void;
|
||||
selectedItem: DeviceEntity;
|
||||
}
|
||||
|
||||
const CustomizationDialog = ({
|
||||
const CustomizationsDialog = ({
|
||||
open,
|
||||
onClose,
|
||||
onSave,
|
||||
selectedItem
|
||||
}: SettingsCustomizationDialogProps) => {
|
||||
}: SettingsCustomizationsDialogProps) => {
|
||||
const { LL } = useI18nContext();
|
||||
const [editItem, setEditItem] = useState<DeviceEntity>(selectedItem);
|
||||
const [error, setError] = useState<boolean>(false);
|
||||
@@ -175,4 +175,4 @@ const CustomizationDialog = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomizationDialog;
|
||||
export default CustomizationsDialog;
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { FC } from 'react';
|
||||
import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/ai';
|
||||
import { CgSmartHomeBoiler } from 'react-icons/cg';
|
||||
import { FaSolarPanel } from 'react-icons/fa';
|
||||
@@ -16,12 +15,8 @@ import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
|
||||
|
||||
import { DeviceType } from './types';
|
||||
|
||||
interface DeviceIconProps {
|
||||
type_id: number;
|
||||
}
|
||||
|
||||
const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
|
||||
switch (type_id as DeviceType) {
|
||||
const DeviceIcon = ({ type_id }: { type_id: DeviceType }) => {
|
||||
switch (type_id) {
|
||||
case DeviceType.TEMPERATURESENSOR:
|
||||
case DeviceType.ANALOGSENSOR:
|
||||
return <MdOutlineSensors />;
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
useLayoutEffect,
|
||||
useState
|
||||
} from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { IconContext } from 'react-icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
@@ -20,13 +19,13 @@ import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
|
||||
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import StarIcon from '@mui/icons-material/Star';
|
||||
import StarBorderOutlinedIcon from '@mui/icons-material/StarBorderOutlined';
|
||||
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
@@ -57,12 +56,12 @@ import {
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import type { Action, State } from '@table-library/react-table-library/types/common';
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova';
|
||||
import { ButtonRow, MessageBox, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { MessageBox, SectionContent, useLayoutTitle } from 'components';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
import { readCoreData, readDeviceData, writeDeviceValue } from '../../api/app';
|
||||
import DeviceIcon from './DeviceIcon';
|
||||
import DashboardDevicesDialog from './DevicesDialog';
|
||||
import { formatValue } from './deviceValue';
|
||||
@@ -70,7 +69,7 @@ import { DeviceEntityMask, DeviceType, DeviceValueUOM_s } from './types';
|
||||
import type { Device, DeviceValue } from './types';
|
||||
import { deviceValueItemValidation } from './validators';
|
||||
|
||||
const Devices: FC = () => {
|
||||
const Devices = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
|
||||
@@ -85,18 +84,15 @@ const Devices: FC = () => {
|
||||
|
||||
useLayoutTitle(LL.DEVICES());
|
||||
|
||||
const { data: coreData, send: readCoreData } = useRequest(
|
||||
() => EMSESP.readCoreData(),
|
||||
{
|
||||
const { data: coreData, send: sendCoreData } = useRequest(() => readCoreData(), {
|
||||
initialData: {
|
||||
connected: true,
|
||||
devices: []
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const { data: deviceData, send: readDeviceData } = useRequest(
|
||||
(id: number) => EMSESP.readDeviceData(id),
|
||||
const { data: deviceData, send: sendDeviceData } = useRequest(
|
||||
(id: number) => readDeviceData(id),
|
||||
{
|
||||
initialData: {
|
||||
data: []
|
||||
@@ -105,8 +101,8 @@ const Devices: FC = () => {
|
||||
}
|
||||
);
|
||||
|
||||
const { loading: submitting, send: writeDeviceValue } = useRequest(
|
||||
(data: { id: number; c: string; v: unknown }) => EMSESP.writeDeviceValue(data),
|
||||
const { loading: submitting, send: sendDeviceValue } = useRequest(
|
||||
(data: { id: number; c: string; v: unknown }) => writeDeviceValue(data),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
@@ -288,7 +284,7 @@ const Devices: FC = () => {
|
||||
async function onSelectChange(action: Action, state: State) {
|
||||
setSelectedDevice(state.id as number);
|
||||
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]);
|
||||
|
||||
const refreshData = () => {
|
||||
if (!deviceValueDialogOpen) {
|
||||
selectedDevice ? void readDeviceData(selectedDevice) : void readCoreData();
|
||||
}
|
||||
};
|
||||
|
||||
const customize = () => {
|
||||
if (selectedDevice == 99) {
|
||||
navigate('/customentities');
|
||||
@@ -437,7 +427,12 @@ const Devices: FC = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => refreshData(), 60000);
|
||||
const timer = setInterval(() => {
|
||||
if (deviceValueDialogOpen) {
|
||||
return;
|
||||
}
|
||||
selectedDevice ? void sendDeviceData(selectedDevice) : void sendCoreData();
|
||||
}, 2000);
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
@@ -445,7 +440,7 @@ const Devices: FC = () => {
|
||||
|
||||
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
|
||||
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(() => {
|
||||
toast.success(LL.WRITE_CMD_SENT());
|
||||
})
|
||||
@@ -454,7 +449,7 @@ const Devices: FC = () => {
|
||||
})
|
||||
.finally(async () => {
|
||||
setDeviceValueDialogOpen(false);
|
||||
await readDeviceData(id);
|
||||
await sendDeviceData(id);
|
||||
setSelectedDeviceValue(undefined);
|
||||
});
|
||||
};
|
||||
@@ -568,6 +563,9 @@ const Devices: FC = () => {
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
<Body>
|
||||
{tableList.length === 0 && (
|
||||
<CircularProgress sx={{ margin: 1 }} size={24} />
|
||||
)}
|
||||
{tableList.map((device: Device) => (
|
||||
<Row key={device.id} item={device}>
|
||||
<Cell stiff>
|
||||
@@ -592,6 +590,7 @@ const Devices: FC = () => {
|
||||
|
||||
const deviceValueDialogClose = () => {
|
||||
setDeviceValueDialogOpen(false);
|
||||
void sendDeviceData(selectedDevice);
|
||||
};
|
||||
|
||||
const renderDeviceData = () => {
|
||||
@@ -685,11 +684,6 @@ const Devices: FC = () => {
|
||||
)}
|
||||
</IconButton>
|
||||
</ButtonTooltip>
|
||||
<ButtonTooltip title={LL.REFRESH()}>
|
||||
<IconButton onClick={refreshData}>
|
||||
<RefreshIcon color="primary" sx={{ fontSize: 18 }} />
|
||||
</IconButton>
|
||||
</ButtonTooltip>
|
||||
</Typography>
|
||||
<Grid item zeroMinWidth justifyContent="flex-end">
|
||||
<ButtonTooltip title={LL.CANCEL()}>
|
||||
@@ -784,16 +778,6 @@ const Devices: FC = () => {
|
||||
progress={submitting}
|
||||
/>
|
||||
)}
|
||||
<ButtonRow mt={1}>
|
||||
<Button
|
||||
startIcon={<RefreshIcon />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={refreshData}
|
||||
>
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { FC } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CommentIcon from '@mui/icons-material/CommentTwoTone';
|
||||
@@ -18,25 +17,20 @@ import {
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
|
||||
import * as EMSESP from 'app/main/api';
|
||||
import { useRequest } from 'alova';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import { API } from '../../api/app';
|
||||
import type { APIcall } from './types';
|
||||
|
||||
const Help: FC = () => {
|
||||
const Help = () => {
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.HELP_OF(''));
|
||||
|
||||
const { send: getAPI, onSuccess: onGetAPI } = useRequest(
|
||||
(data: APIcall) => EMSESP.API(data),
|
||||
{
|
||||
const { send: getAPI } = useRequest((data: APIcall) => API(data), {
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
|
||||
onGetAPI((event) => {
|
||||
}).onSuccess((event) => {
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = URL.createObjectURL(
|
||||
new Blob([JSON.stringify(event.data, null, 2)], {
|
||||
@@ -45,7 +39,7 @@ const Help: FC = () => {
|
||||
);
|
||||
|
||||
anchor.download =
|
||||
'emsesp_' + event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt';
|
||||
'emsesp_' + event.args[0].device + '_' + event.args[0].entity + '.txt';
|
||||
anchor.click();
|
||||
URL.revokeObjectURL(anchor.href);
|
||||
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
||||
@@ -58,6 +52,7 @@ const Help: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SectionContent>
|
||||
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
|
||||
<ListItem>
|
||||
@@ -139,6 +134,7 @@ const Help: FC = () => {
|
||||
</Typography>
|
||||
</Box>
|
||||
</SectionContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useBlocker } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
@@ -18,7 +17,7 @@ import {
|
||||
Table
|
||||
} from '@table-library/react-table-library/table';
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import { updateState, useRequest } from 'alova';
|
||||
import { updateState, useRequest } from 'alova/client';
|
||||
import {
|
||||
BlockNavigation,
|
||||
ButtonRow,
|
||||
@@ -28,11 +27,11 @@ import {
|
||||
} from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
import { readModules, writeModules } from '../../api/app';
|
||||
import ModulesDialog from './ModulesDialog';
|
||||
import type { ModuleItem, Modules } from './types';
|
||||
|
||||
const Modules: FC = () => {
|
||||
const Modules = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const [numChanges, setNumChanges] = useState<number>(0);
|
||||
const blocker = useBlocker(numChanges !== 0);
|
||||
@@ -44,13 +43,12 @@ const Modules: FC = () => {
|
||||
data: modules,
|
||||
send: fetchModules,
|
||||
error
|
||||
} = useRequest(EMSESP.readModules, {
|
||||
} = useRequest(readModules, {
|
||||
initialData: []
|
||||
});
|
||||
|
||||
const { send: writeModules } = useRequest(
|
||||
(data: { key: string; enabled: boolean; license: string }) =>
|
||||
EMSESP.writeModules(data),
|
||||
const { send: updateModules } = useRequest(
|
||||
(data: { key: string; enabled: boolean; license: string }) => writeModules(data),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
@@ -123,7 +121,7 @@ const Modules: FC = () => {
|
||||
}
|
||||
|
||||
const updateModuleItem = (updatedItem: ModuleItem) => {
|
||||
updateState('modules', (data: ModuleItem[]) => {
|
||||
void updateState(readModules(), (data: ModuleItem[]) => {
|
||||
const new_data = data.map((mi) =>
|
||||
mi.id === updatedItem.id ? { ...mi, ...updatedItem } : mi
|
||||
);
|
||||
@@ -133,7 +131,7 @@ const Modules: FC = () => {
|
||||
};
|
||||
|
||||
const saveModules = async () => {
|
||||
await writeModules({
|
||||
await updateModules({
|
||||
modules: modules.map((condensed_mi) => ({
|
||||
key: condensed_mi.key,
|
||||
enabled: condensed_mi.enabled,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { FC } from 'react';
|
||||
|
||||
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
||||
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||
@@ -32,12 +30,7 @@ const OPTION_ICONS: {
|
||||
favorite: [StarIcon, StarOutlineIcon]
|
||||
};
|
||||
|
||||
interface OptionIconProps {
|
||||
type: OptionType;
|
||||
isSet: boolean;
|
||||
}
|
||||
|
||||
const OptionIcon: FC<OptionIconProps> = ({ type, isSet }) => {
|
||||
const OptionIcon = ({ type, isSet }: { type: OptionType; isSet: boolean }) => {
|
||||
const Icon = OPTION_ICONS[type][isSet ? 0 : 1];
|
||||
return isSet ? (
|
||||
<Icon color="primary" sx={{ fontSize: 16, verticalAlign: 'middle' }} />
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useBlocker } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
@@ -19,7 +18,7 @@ import {
|
||||
Table
|
||||
} from '@table-library/react-table-library/table';
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import { updateState, useRequest } from 'alova';
|
||||
import { updateState, useRequest } from 'alova/client';
|
||||
import {
|
||||
BlockNavigation,
|
||||
ButtonRow,
|
||||
@@ -29,13 +28,13 @@ import {
|
||||
} from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
import { readSchedule, writeSchedule } from '../../api/app';
|
||||
import SettingsSchedulerDialog from './SchedulerDialog';
|
||||
import { ScheduleFlag } from './types';
|
||||
import type { Schedule, ScheduleItem } from './types';
|
||||
import { schedulerItemValidation } from './validators';
|
||||
|
||||
const Scheduler: FC = () => {
|
||||
const Scheduler = () => {
|
||||
const { LL, locale } = useI18nContext();
|
||||
const [numChanges, setNumChanges] = useState<number>(0);
|
||||
const blocker = useBlocker(numChanges !== 0);
|
||||
@@ -48,13 +47,12 @@ const Scheduler: FC = () => {
|
||||
data: schedule,
|
||||
send: fetchSchedule,
|
||||
error
|
||||
} = useRequest(EMSESP.readSchedule, {
|
||||
initialData: [],
|
||||
force: true
|
||||
} = useRequest(readSchedule, {
|
||||
initialData: []
|
||||
});
|
||||
|
||||
const { send: writeSchedule } = useRequest(
|
||||
(data: Schedule) => EMSESP.writeSchedule(data),
|
||||
const { send: updateSchedule } = useRequest(
|
||||
(data: Schedule) => writeSchedule(data),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
@@ -131,7 +129,7 @@ const Scheduler: FC = () => {
|
||||
});
|
||||
|
||||
const saveSchedule = async () => {
|
||||
await writeSchedule({
|
||||
await updateSchedule({
|
||||
schedule: schedule
|
||||
.filter((si) => !si.deleted)
|
||||
.map((condensed_si) => ({
|
||||
@@ -177,8 +175,7 @@ const Scheduler: FC = () => {
|
||||
|
||||
const onDialogSave = (updatedItem: ScheduleItem) => {
|
||||
setDialogOpen(false);
|
||||
|
||||
updateState('schedule', (data: ScheduleItem[]) => {
|
||||
void updateState(readSchedule(), (data: ScheduleItem[]) => {
|
||||
const new_data = creating
|
||||
? [
|
||||
...data.filter((si) => creating || si.o_id !== updatedItem.o_id),
|
||||
|
||||
@@ -208,7 +208,7 @@ const SchedulerDialog = ({
|
||||
scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE ? 'primary' : 'grey'
|
||||
}
|
||||
>
|
||||
{LL.ONCHANGE(0)}
|
||||
{LL.ONCHANGE()}
|
||||
</Typography>
|
||||
</ToggleButton>
|
||||
<ToggleButton value={ScheduleFlag.SCHEDULE_CONDITION}>
|
||||
@@ -218,7 +218,7 @@ const SchedulerDialog = ({
|
||||
scheduleType === ScheduleFlag.SCHEDULE_CONDITION ? 'primary' : 'grey'
|
||||
}
|
||||
>
|
||||
{LL.CONDITION(0)}
|
||||
{LL.CONDITION()}
|
||||
</Typography>
|
||||
</ToggleButton>
|
||||
<ToggleButton value={ScheduleFlag.SCHEDULE_IMMEDIATE}>
|
||||
@@ -228,7 +228,7 @@ const SchedulerDialog = ({
|
||||
scheduleType === ScheduleFlag.SCHEDULE_IMMEDIATE ? 'primary' : 'grey'
|
||||
}
|
||||
>
|
||||
{LL.IMMEDIATE(0)}
|
||||
{LL.IMMEDIATE()}
|
||||
</Typography>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
@@ -311,10 +311,10 @@ const SchedulerDialog = ({
|
||||
name="time"
|
||||
label={
|
||||
scheduleType === ScheduleFlag.SCHEDULE_CONDITION
|
||||
? LL.CONDITION(1)
|
||||
? LL.CONDITION()
|
||||
: scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE
|
||||
? LL.ONCHANGE(1)
|
||||
: LL.IMMEDIATE(1)
|
||||
? LL.ONCHANGE()
|
||||
: LL.IMMEDIATE()
|
||||
}
|
||||
multiline
|
||||
fullWidth
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useContext, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
|
||||
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
|
||||
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
|
||||
@@ -21,12 +19,16 @@ import {
|
||||
} from '@table-library/react-table-library/table';
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import type { State } from '@table-library/react-table-library/types/common';
|
||||
import { useRequest } from 'alova';
|
||||
import { ButtonRow, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useAutoRequest, useRequest } from 'alova/client';
|
||||
import { SectionContent, useLayoutTitle } from 'components';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
import {
|
||||
readSensorData,
|
||||
writeAnalogSensor,
|
||||
writeTemperatureSensor
|
||||
} from '../../api/app';
|
||||
import DashboardSensorsAnalogDialog from './SensorsAnalogDialog';
|
||||
import DashboardSensorsTemperatureDialog from './SensorsTemperatureDialog';
|
||||
import {
|
||||
@@ -46,7 +48,7 @@ import {
|
||||
temperatureSensorItemValidation
|
||||
} from './validators';
|
||||
|
||||
const Sensors: FC = () => {
|
||||
const Sensors = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
|
||||
@@ -57,27 +59,25 @@ const Sensors: FC = () => {
|
||||
const [analogDialogOpen, setAnalogDialogOpen] = useState<boolean>(false);
|
||||
const [creating, setCreating] = useState<boolean>(false);
|
||||
|
||||
const { data: sensorData, send: fetchSensorData } = useRequest(
|
||||
() => EMSESP.readSensorData(),
|
||||
{
|
||||
const { data: sensorData } = useAutoRequest(() => readSensorData(), {
|
||||
initialData: {
|
||||
ts: [],
|
||||
as: [],
|
||||
analog_enabled: false,
|
||||
platform: 'ESP32'
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
pollingTime: 2000
|
||||
});
|
||||
|
||||
const { send: writeTemperatureSensor } = useRequest(
|
||||
(data: WriteTemperatureSensor) => EMSESP.writeTemperatureSensor(data),
|
||||
const { send: sendTemperatureSensor } = useRequest(
|
||||
(data: WriteTemperatureSensor) => writeTemperatureSensor(data),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
|
||||
const { send: writeAnalogSensor } = useRequest(
|
||||
(data: WriteAnalogSensor) => EMSESP.writeAnalogSensor(data),
|
||||
const { send: sendAnalogSensor } = useRequest(
|
||||
(data: WriteAnalogSensor) => writeAnalogSensor(data),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
@@ -195,13 +195,6 @@ const Sensors: FC = () => {
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => fetchSensorData(), 30000);
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
});
|
||||
|
||||
useLayoutTitle(LL.SENSORS());
|
||||
|
||||
const formatDurationMin = (duration_min: number) => {
|
||||
@@ -266,17 +259,16 @@ const Sensors: FC = () => {
|
||||
};
|
||||
|
||||
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(() => {
|
||||
toast.success(LL.UPDATED_OF(LL.SENSOR(1)));
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(LL.UPDATE_OF(LL.SENSOR(2)) + ' ' + LL.FAILED(1));
|
||||
})
|
||||
.finally(async () => {
|
||||
.finally(() => {
|
||||
setTemperatureDialogOpen(false);
|
||||
setSelectedTemperatureSensor(undefined);
|
||||
await fetchSensorData();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -311,7 +303,7 @@ const Sensors: FC = () => {
|
||||
};
|
||||
|
||||
const onAnalogDialogSave = async (as: AnalogSensor) => {
|
||||
await writeAnalogSensor({
|
||||
await sendAnalogSensor({
|
||||
id: as.id,
|
||||
gpio: as.g,
|
||||
name: as.n,
|
||||
@@ -327,10 +319,9 @@ const Sensors: FC = () => {
|
||||
.catch(() => {
|
||||
toast.error(LL.UPDATE_OF(LL.ANALOG_SENSOR(5)) + ' ' + LL.FAILED(1));
|
||||
})
|
||||
.finally(async () => {
|
||||
.finally(() => {
|
||||
setAnalogDialogOpen(false);
|
||||
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">
|
||||
{LL.ANALOG_SENSORS()}
|
||||
</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 && (
|
||||
<Box mt={1} display="flex" flexWrap="wrap" justifyContent="flex-end">
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
startIcon={<AddCircleOutlineOutlinedIcon />}
|
||||
onClick={addAnalogSensor}
|
||||
>
|
||||
{LL.ADD(0) + ' ' + LL.ANALOG_SENSOR(1)}
|
||||
{LL.ADD(0)}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</ButtonRow>
|
||||
)}
|
||||
</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
@@ -19,7 +18,6 @@ import {
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { range } from 'lodash-es';
|
||||
import type { APSettingsType } from 'types';
|
||||
import { APProvisionMode } from 'types';
|
||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||
@@ -29,7 +27,7 @@ export const isAPEnabled = ({ provision_mode }: APSettingsType) =>
|
||||
provision_mode === APProvisionMode.AP_MODE_ALWAYS ||
|
||||
provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
|
||||
|
||||
const APSettings: FC = () => {
|
||||
const APSettings = () => {
|
||||
const {
|
||||
loadData,
|
||||
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 (
|
||||
<>
|
||||
<ValidatedTextField
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -17,9 +16,9 @@ import {
|
||||
Typography
|
||||
} 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 type { ValidateFieldsError } from 'async-validator';
|
||||
import {
|
||||
@@ -36,7 +35,7 @@ import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||
import { validate } from 'validators';
|
||||
|
||||
import * as EMSESP from '../main/api';
|
||||
import { getBoardProfile, readSettings, writeSettings } from '../../api/app';
|
||||
import { BOARD_PROFILES } from '../main/types';
|
||||
import type { Settings } from '../main/types';
|
||||
import { createSettingsValidator } from '../main/validators';
|
||||
@@ -49,15 +48,12 @@ export function boardProfileSelectItems() {
|
||||
));
|
||||
}
|
||||
|
||||
const ApplicationSettings: FC = () => {
|
||||
const { data: hardwareData } = useRequest(SystemApi.readHardwareStatus, {
|
||||
force: true
|
||||
});
|
||||
const ApplicationSettings = () => {
|
||||
const { data: hardwareData } = useRequest(readHardwareStatus);
|
||||
|
||||
const {
|
||||
loadData,
|
||||
saveData,
|
||||
saving,
|
||||
updateDataValue,
|
||||
data,
|
||||
origData,
|
||||
@@ -67,8 +63,8 @@ const ApplicationSettings: FC = () => {
|
||||
errorMessage,
|
||||
restartNeeded
|
||||
} = useRest<Settings>({
|
||||
read: EMSESP.readSettings,
|
||||
update: EMSESP.writeSettings
|
||||
read: readSettings,
|
||||
update: writeSettings
|
||||
});
|
||||
|
||||
const [restarting, setRestarting] = useState<boolean>();
|
||||
@@ -84,19 +80,12 @@ const ApplicationSettings: FC = () => {
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
|
||||
const {
|
||||
loading: processingBoard,
|
||||
send: readBoardProfile,
|
||||
onSuccess: onSuccessBoardProfile
|
||||
} = useRequest((boardProfile: string) => EMSESP.getBoardProfile(boardProfile), {
|
||||
const { loading: processingBoard, send: readBoardProfile } = useRequest(
|
||||
(boardProfile: string) => getBoardProfile(boardProfile),
|
||||
{
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
onSuccessBoardProfile((event) => {
|
||||
}
|
||||
).onSuccess((event) => {
|
||||
const response = event.data as Settings;
|
||||
updateDataValue({
|
||||
...data,
|
||||
@@ -113,6 +102,10 @@ const ApplicationSettings: FC = () => {
|
||||
});
|
||||
});
|
||||
|
||||
const { send: restartCommand } = useRequest(restart(), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const updateBoardProfile = async (board_profile: string) => {
|
||||
await readBoardProfile(board_profile).catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
@@ -122,7 +115,7 @@ const ApplicationSettings: FC = () => {
|
||||
useLayoutTitle(LL.SETTINGS_OF(LL.APPLICATION()));
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
if (!data || !hardwareData) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
}
|
||||
|
||||
@@ -161,16 +154,321 @@ const ApplicationSettings: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<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}>"true"/"false"</MenuItem>
|
||||
<MenuItem value={4}>true/false</MenuItem>
|
||||
<MenuItem value={5}>"1"/"0"</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>
|
||||
<Box color="warning.main">
|
||||
<Typography variant="body2">{LL.BOARD_PROFILE_TEXT()}</Typography>
|
||||
</Box>
|
||||
<TextField
|
||||
name="board_profile"
|
||||
label={LL.BOARD_PROFILE()}
|
||||
value={data.board_profile}
|
||||
disabled={processingBoard}
|
||||
disabled={processingBoard || hardwareData.model.startsWith('BBQKees')}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={changeBoardProfile}
|
||||
@@ -204,7 +502,6 @@ const ApplicationSettings: FC = () => {
|
||||
type="number"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
@@ -218,7 +515,6 @@ const ApplicationSettings: FC = () => {
|
||||
type="number"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
@@ -232,7 +528,6 @@ const ApplicationSettings: FC = () => {
|
||||
type="number"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
@@ -248,7 +543,6 @@ const ApplicationSettings: FC = () => {
|
||||
type="number"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
@@ -262,14 +556,12 @@ const ApplicationSettings: FC = () => {
|
||||
type="number"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<TextField
|
||||
name="phy_type"
|
||||
label={LL.PHY_TYPE()}
|
||||
disabled={saving}
|
||||
value={data.phy_type}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -302,7 +594,6 @@ const ApplicationSettings: FC = () => {
|
||||
type="number"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
@@ -315,14 +606,12 @@ const ApplicationSettings: FC = () => {
|
||||
type="number"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<TextField
|
||||
name="eth_clock_mode"
|
||||
label="PHY Clk"
|
||||
disabled={saving}
|
||||
value={data.eth_clock_mode}
|
||||
fullWidth
|
||||
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
|
||||
container
|
||||
spacing={1}
|
||||
@@ -354,7 +640,6 @@ const ApplicationSettings: FC = () => {
|
||||
<TextField
|
||||
name="tx_mode"
|
||||
label={LL.TX_MODE()}
|
||||
disabled={saving}
|
||||
value={data.tx_mode}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -371,8 +656,7 @@ const ApplicationSettings: FC = () => {
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
name="ems_bus_id"
|
||||
label={LL.ID_OF(LL.EMS_BUS(1))}
|
||||
disabled={saving}
|
||||
label={LL.ID_OF(LL.EMS_BUS(0))}
|
||||
value={data.ems_bus_id}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -394,33 +678,16 @@ const ApplicationSettings: FC = () => {
|
||||
</TextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
{LL.GENERAL_OPTIONS()}
|
||||
</Typography>
|
||||
<Grid item>
|
||||
<TextField
|
||||
name="locale"
|
||||
label={LL.LANGUAGE_ENTITIES()}
|
||||
disabled={saving}
|
||||
value={data.locale}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.readonly_mode}
|
||||
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>
|
||||
name="readonly_mode"
|
||||
/>
|
||||
}
|
||||
label={LL.READONLY()}
|
||||
/>
|
||||
{data.led_gpio !== 0 && (
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
@@ -431,64 +698,8 @@ const ApplicationSettings: FC = () => {
|
||||
/>
|
||||
}
|
||||
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
|
||||
control={
|
||||
<Checkbox
|
||||
@@ -498,8 +709,11 @@ const ApplicationSettings: FC = () => {
|
||||
/>
|
||||
}
|
||||
label={LL.UNDERCLOCK_CPU()}
|
||||
disabled={saving}
|
||||
/>
|
||||
|
||||
<Typography sx={{ pb: 1, pt: 2 }} variant="h6" color="primary">
|
||||
{LL.GENERAL_OPTIONS()}
|
||||
</Typography>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
@@ -509,16 +723,8 @@ const ApplicationSettings: FC = () => {
|
||||
/>
|
||||
}
|
||||
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
|
||||
control={
|
||||
<Checkbox
|
||||
@@ -529,9 +735,8 @@ const ApplicationSettings: FC = () => {
|
||||
}
|
||||
label={LL.REMOTE_TIMEOUT_EN()}
|
||||
/>
|
||||
</Grid>
|
||||
{data.remote_timeout_en && (
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<Box mt={2}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="remote_timeout"
|
||||
@@ -541,15 +746,13 @@ const ApplicationSettings: FC = () => {
|
||||
<InputAdornment position="end">{LL.HOURS()}</InputAdornment>
|
||||
)
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.remote_timeout)}
|
||||
type="number"
|
||||
onChange={updateFormValue}
|
||||
/>
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid
|
||||
container
|
||||
spacing={0}
|
||||
@@ -566,7 +769,6 @@ const ApplicationSettings: FC = () => {
|
||||
/>
|
||||
}
|
||||
label={LL.ENABLE_SHOWER_TIMER()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
@@ -649,256 +851,7 @@ const ApplicationSettings: FC = () => {
|
||||
</>
|
||||
)}
|
||||
</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}>"true"/"false"</MenuItem>
|
||||
<MenuItem value={4}>true/false</MenuItem>
|
||||
<MenuItem value={5}>"1"/"0"</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 && (
|
||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
|
||||
<Button
|
||||
@@ -915,7 +868,6 @@ const ApplicationSettings: FC = () => {
|
||||
<ButtonRow>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
disabled={saving}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
type="submit"
|
||||
@@ -925,7 +877,6 @@ const ApplicationSettings: FC = () => {
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<WarningIcon color="warning" />}
|
||||
disabled={saving}
|
||||
variant="contained"
|
||||
color="info"
|
||||
type="submit"
|
||||
|
||||
295
interface/src/app/settings/DownloadUpload.tsx
Normal file
295
interface/src/app/settings/DownloadUpload.tsx
Normal 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> ({getPlatform()})
|
||||
<Divider />
|
||||
{latestVersion && (
|
||||
<Box mt={2}>
|
||||
{LL.THE_LATEST()} {LL.OFFICIAL()} {LL.RELEASE_IS()}
|
||||
<b>{latestVersion}</b>
|
||||
(
|
||||
<Link target="_blank" href={STABLE_RELNOTES_URL} color="primary">
|
||||
{LL.RELEASE_NOTES()}
|
||||
</Link>
|
||||
) (
|
||||
<Link
|
||||
target="_blank"
|
||||
href={
|
||||
STABLE_URL + 'v' + latestVersion + '/' + getBinURL(latestVersion)
|
||||
}
|
||||
color="primary"
|
||||
>
|
||||
{LL.DOWNLOAD(1)}
|
||||
</Link>
|
||||
)
|
||||
</Box>
|
||||
)}
|
||||
{latestDevVersion && (
|
||||
<Box mt={2}>
|
||||
{LL.THE_LATEST()} {LL.DEVELOPMENT()} {LL.RELEASE_IS()}
|
||||
|
||||
<b>{latestDevVersion}</b>
|
||||
(
|
||||
<Link target="_blank" href={DEV_RELNOTES_URL} color="primary">
|
||||
{LL.RELEASE_NOTES()}
|
||||
</Link>
|
||||
) (
|
||||
<Link
|
||||
target="_blank"
|
||||
href={DEV_URL + getBinURL(latestDevVersion)}
|
||||
color="primary"
|
||||
>
|
||||
{LL.DOWNLOAD(1)}
|
||||
</Link>
|
||||
)
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<SingleUpload />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return <SectionContent>{content()}</SectionContent>;
|
||||
};
|
||||
|
||||
export default DownloadUpload;
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
@@ -31,7 +30,7 @@ import type { MqttSettingsType } from 'types';
|
||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||
import { createMqttSettingsValidator, validate } from 'validators';
|
||||
|
||||
const MqttSettings: FC = () => {
|
||||
const MqttSettings = () => {
|
||||
const {
|
||||
loadData,
|
||||
saving,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { Button, Checkbox, MenuItem } from '@mui/material';
|
||||
|
||||
import * 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 {
|
||||
BlockFormControlLabel,
|
||||
@@ -26,7 +26,7 @@ import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
|
||||
|
||||
import { TIME_ZONES, selectedTimeZone, timeZoneSelectItems } from './TZ';
|
||||
|
||||
const NTPSettings: FC = () => {
|
||||
const NTPSettings = () => {
|
||||
const {
|
||||
loadData,
|
||||
saving,
|
||||
@@ -72,8 +72,7 @@ const NTPSettings: FC = () => {
|
||||
|
||||
const changeTimeZone = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateFormValue(event);
|
||||
|
||||
updateState('ntpSettings', (settings: NTPSettingsType) => ({
|
||||
void updateState(readNTPSettings(), (settings: NTPSettingsType) => ({
|
||||
...settings,
|
||||
tz_label: event.target.value,
|
||||
tz_format: TIME_ZONES[event.target.value]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type FC, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -24,12 +24,12 @@ import {
|
||||
import * as SystemApi from 'api/system';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { SectionContent, useLayoutTitle } from 'components';
|
||||
import ListMenuItem from 'components/layout/ListMenuItem';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
const Settings: FC = () => {
|
||||
const Settings = () => {
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.SETTINGS(0));
|
||||
|
||||
@@ -136,8 +136,8 @@ const Settings: FC = () => {
|
||||
<ListMenuItem
|
||||
icon={ImportExportIcon}
|
||||
bgcolor="#5d89f7"
|
||||
label={LL.UPLOAD_DOWNLOAD()}
|
||||
text={LL.UPLOAD_DOWNLOAD_1()}
|
||||
label={LL.DOWNLOAD_UPLOAD()}
|
||||
text={LL.DOWNLOAD_UPLOAD_1()}
|
||||
to="upload"
|
||||
/>
|
||||
</List>
|
||||
|
||||
@@ -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> ({getPlatform()})
|
||||
<Divider />
|
||||
{latestVersion && (
|
||||
<Box mt={2}>
|
||||
{LL.THE_LATEST()} {LL.OFFICIAL()} {LL.RELEASE_IS()}
|
||||
<b>{latestVersion}</b>
|
||||
(
|
||||
<Link target="_blank" href={STABLE_RELNOTES_URL} color="primary">
|
||||
{LL.RELEASE_NOTES()}
|
||||
</Link>
|
||||
) (
|
||||
<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()} {LL.DEVELOPMENT()} {LL.RELEASE_IS()}
|
||||
|
||||
<b>{latestDevVersion}</b>
|
||||
(
|
||||
<Link target="_blank" href={DEV_RELNOTES_URL} color="primary">
|
||||
{LL.RELEASE_NOTES()}
|
||||
</Link>
|
||||
) (
|
||||
<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;
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { Navigate, Route, Routes, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { Tab } from '@mui/material';
|
||||
@@ -12,7 +11,7 @@ import NetworkSettings from './NetworkSettings';
|
||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||
import WiFiNetworkScanner from './WiFiNetworkScanner';
|
||||
|
||||
const Network: FC = () => {
|
||||
const Network = () => {
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.SETTINGS_OF(LL.NETWORK(0)));
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -26,7 +25,7 @@ import {
|
||||
import * as NetworkApi from 'api/network';
|
||||
import * as SystemApi from 'api/system';
|
||||
|
||||
import { updateState, useRequest } from 'alova';
|
||||
import { updateState, useRequest } from 'alova/client';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import {
|
||||
BlockFormControlLabel,
|
||||
@@ -48,7 +47,7 @@ import RestartMonitor from '../../status/RestartMonitor';
|
||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
|
||||
|
||||
const NetworkSettings: FC = () => {
|
||||
const NetworkSettings = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { selectedNetwork, deselectNetwork } = useContext(WiFiConnectionContext);
|
||||
@@ -80,7 +79,9 @@ const NetworkSettings: FC = () => {
|
||||
useEffect(() => {
|
||||
if (!initialized && data) {
|
||||
if (selectedNetwork) {
|
||||
updateState('networkSettings', (current_data: NetworkSettingsType) => ({
|
||||
void updateState(
|
||||
NetworkApi.readNetworkSettings(),
|
||||
(current_data: NetworkSettingsType) => ({
|
||||
ssid: selectedNetwork.ssid,
|
||||
bssid: selectedNetwork.bssid,
|
||||
password: current_data ? current_data.password : '',
|
||||
@@ -92,7 +93,8 @@ const NetworkSettings: FC = () => {
|
||||
enableMDNS: true,
|
||||
enableCORS: false,
|
||||
CORSOrigin: '*'
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
setInitialized(true);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
|
||||
import { Button } from '@mui/material';
|
||||
|
||||
import * as NetworkApi from 'api/network';
|
||||
|
||||
import { updateState, useRequest } from 'alova';
|
||||
import { updateState, useRequest } from 'alova/client';
|
||||
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
@@ -15,23 +14,28 @@ import WiFiNetworkSelector from './WiFiNetworkSelector';
|
||||
const NUM_POLLS = 10;
|
||||
const POLLING_FREQUENCY = 1000;
|
||||
|
||||
const WiFiNetworkScanner: FC = () => {
|
||||
const WiFiNetworkScanner = () => {
|
||||
const pollCount = useRef(0);
|
||||
const { LL } = useI18nContext();
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
|
||||
const { send: scanNetworks, onComplete: onCompleteScanNetworks } = useRequest(
|
||||
NetworkApi.scanNetworks
|
||||
); // is called on page load to start network scan
|
||||
const {
|
||||
data: networkList,
|
||||
send: getNetworkList,
|
||||
onSuccess: onSuccessNetworkList
|
||||
} = useRequest(NetworkApi.listNetworks, {
|
||||
immediate: false
|
||||
});
|
||||
// is called on page load to start network scan
|
||||
const { send: scanNetworks } = useRequest(NetworkApi.scanNetworks).onComplete(
|
||||
() => {
|
||||
pollCount.current = 0;
|
||||
setErrorMessage(undefined);
|
||||
void updateState(NetworkApi.listNetworks(), () => undefined);
|
||||
void getNetworkList();
|
||||
}
|
||||
);
|
||||
|
||||
onSuccessNetworkList((event) => {
|
||||
const { data: networkList, send: getNetworkList } = useRequest(
|
||||
NetworkApi.listNetworks,
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
).onSuccess((event) => {
|
||||
// is called when network scan is completed
|
||||
if (!event.data) {
|
||||
const completedPollCount = pollCount.current + 1;
|
||||
if (completedPollCount < NUM_POLLS) {
|
||||
@@ -44,13 +48,6 @@ const WiFiNetworkScanner: FC = () => {
|
||||
}
|
||||
});
|
||||
|
||||
onCompleteScanNetworks(() => {
|
||||
pollCount.current = 0;
|
||||
setErrorMessage(undefined);
|
||||
updateState('listNetworks', () => undefined);
|
||||
void getNetworkList();
|
||||
});
|
||||
|
||||
const renderNetworkScanner = () => {
|
||||
if (!networkList) {
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useContext } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import LockOpenIcon from '@mui/icons-material/LockOpen';
|
||||
@@ -23,10 +22,6 @@ import { WiFiEncryptionType } from 'types';
|
||||
|
||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||
|
||||
interface WiFiNetworkSelectorProps {
|
||||
networkList: WiFiNetworkList;
|
||||
}
|
||||
|
||||
export const isNetworkOpen = ({ encryption_type }: WiFiNetwork) =>
|
||||
encryption_type === WiFiEncryptionType.WIFI_AUTH_OPEN;
|
||||
|
||||
@@ -62,7 +57,7 @@ const networkQualityHighlight = ({ rssi }: WiFiNetwork, theme: Theme) => {
|
||||
return theme.palette.success.main;
|
||||
};
|
||||
|
||||
const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => {
|
||||
const WiFiNetworkSelector = ({ networkList }: { networkList: WiFiNetworkList }) => {
|
||||
const { LL } = useI18nContext();
|
||||
const theme = useTheme();
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useEffect } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import {
|
||||
@@ -17,7 +16,7 @@ import {
|
||||
import * as SecurityApi from 'api/security';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { MessageBox } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
@@ -26,7 +25,7 @@ interface GenerateTokenProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
|
||||
const GenerateToken = ({ username, onClose }: GenerateTokenProps) => {
|
||||
const { LL } = useI18nContext();
|
||||
const open = !!username;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useContext, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useBlocker } from 'react-router-dom';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -40,7 +39,7 @@ import { createUserValidator } from 'validators';
|
||||
import GenerateToken from './GenerateToken';
|
||||
import User from './User';
|
||||
|
||||
const ManageUsers: FC = () => {
|
||||
const ManageUsers = () => {
|
||||
const { loadData, saveData, saving, data, updateDataValue, errorMessage } =
|
||||
useRest<SecuritySettingsType>({
|
||||
read: SecurityApi.readSecuritySettings,
|
||||
@@ -221,8 +220,8 @@ const ManageUsers: FC = () => {
|
||||
)}
|
||||
|
||||
<Box display="flex" flexWrap="wrap">
|
||||
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
|
||||
{changed !== 0 && (
|
||||
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
|
||||
<ButtonRow>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
@@ -245,9 +244,8 @@ const ManageUsers: FC = () => {
|
||||
{LL.APPLY_CHANGES(changed)}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
)}
|
||||
<Box flexWrap="nowrap" whiteSpace="nowrap">
|
||||
<ButtonRow>
|
||||
<Button
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { FC } from 'react';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import { Tab } from '@mui/material';
|
||||
@@ -9,7 +8,7 @@ import { useI18nContext } from 'i18n/i18n-react';
|
||||
import ManageUsers from './ManageUsers';
|
||||
import SecuritySettings from './SecuritySettings';
|
||||
|
||||
const Security: FC = () => {
|
||||
const Security = () => {
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.SETTINGS_OF(LL.SECURITY(0)));
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useContext, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
@@ -22,7 +21,7 @@ import type { SecuritySettingsType } from 'types';
|
||||
import { updateValueDirty, useRest } from 'utils';
|
||||
import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators';
|
||||
|
||||
const SecuritySettings: FC = () => {
|
||||
const SecuritySettings = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
|
||||
@@ -29,10 +29,8 @@ import { validate } from 'validators';
|
||||
interface UserFormProps {
|
||||
creating: boolean;
|
||||
validator: Schema;
|
||||
|
||||
user?: UserType;
|
||||
setUser: React.Dispatch<React.SetStateAction<UserType | undefined>>;
|
||||
|
||||
onDoneEditing: () => void;
|
||||
onCancelEditing: () => void;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import type { FC } from 'react';
|
||||
|
||||
import ComputerIcon from '@mui/icons-material/Computer';
|
||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
@@ -18,8 +14,8 @@ import type { Theme } from '@mui/material';
|
||||
|
||||
import * as APApi from 'api/ap';
|
||||
|
||||
import { useRequest } from 'alova';
|
||||
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useAutoRequest } from 'alova/client';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { APStatusType } from 'types';
|
||||
import { APNetworkStatus } from 'types';
|
||||
@@ -37,8 +33,12 @@ export const apStatusHighlight = ({ status }: APStatusType, theme: Theme) => {
|
||||
}
|
||||
};
|
||||
|
||||
const APStatus: FC = () => {
|
||||
const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus);
|
||||
const APStatus = () => {
|
||||
const {
|
||||
data: data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(APApi.readAPStatus, { pollingTime: 5000 });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.STATUS_OF(LL.ACCESS_POINT(0)));
|
||||
@@ -64,7 +64,6 @@ const APStatus: FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
@@ -79,10 +78,7 @@ const APStatus: FC = () => {
|
||||
<ListItemAvatar>
|
||||
<Avatar>IP</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={LL.ADDRESS_OF('IP')}
|
||||
secondary={data.ip_address}
|
||||
/>
|
||||
<ListItemText primary={LL.ADDRESS_OF('IP')} secondary={data.ip_address} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -107,17 +103,6 @@ const APStatus: FC = () => {
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</List>
|
||||
<ButtonRow>
|
||||
<Button
|
||||
startIcon={<RefreshIcon />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={loadData}
|
||||
>
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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 {
|
||||
Body,
|
||||
Cell,
|
||||
@@ -14,16 +8,20 @@ import {
|
||||
Table
|
||||
} from '@table-library/react-table-library/table';
|
||||
import { useTheme as tableTheme } from '@table-library/react-table-library/theme';
|
||||
import { useRequest } from 'alova';
|
||||
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useAutoRequest } from 'alova/client';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { Translation } from 'i18n/i18n-types';
|
||||
|
||||
import * as EMSESP from '../main/api';
|
||||
import { readActivity } from '../../api/app';
|
||||
import type { Stat } from '../main/types';
|
||||
|
||||
const SystemActivity: FC = () => {
|
||||
const { data: data, send: loadData, error } = useRequest(EMSESP.readActivity);
|
||||
const SystemActivity = () => {
|
||||
const {
|
||||
data: data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(readActivity, { pollingTime: 2000 });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
@@ -67,13 +65,6 @@ const SystemActivity: FC = () => {
|
||||
`
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => loadData(), 30000);
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
});
|
||||
|
||||
const showName = (id: number) => {
|
||||
const name: keyof Translation['STATUS_NAMES'] = id;
|
||||
return LL.STATUS_NAMES[name]();
|
||||
@@ -99,7 +90,6 @@ const SystemActivity: FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table
|
||||
data={{ nodes: data.stats }}
|
||||
theme={stats_theme}
|
||||
@@ -128,17 +118,6 @@ const SystemActivity: FC = () => {
|
||||
</>
|
||||
)}
|
||||
</Table>
|
||||
<ButtonRow mt={1}>
|
||||
<Button
|
||||
startIcon={<RefreshIcon />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={loadData}
|
||||
>
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import type { FC } from 'react';
|
||||
|
||||
import AppsIcon from '@mui/icons-material/Apps';
|
||||
import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard';
|
||||
import DevicesIcon from '@mui/icons-material/Devices';
|
||||
import FolderIcon from '@mui/icons-material/Folder';
|
||||
import MemoryIcon from '@mui/icons-material/Memory';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import SdCardAlertIcon from '@mui/icons-material/SdCardAlert';
|
||||
import SdStorageIcon from '@mui/icons-material/SdStorage';
|
||||
import TapAndPlayIcon from '@mui/icons-material/TapAndPlay';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
@@ -22,8 +17,8 @@ import {
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
|
||||
import { useRequest } from 'alova';
|
||||
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useAutoRequest } from 'alova/client';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import BBQKeesIcon from './bbqkees.svg';
|
||||
@@ -32,7 +27,7 @@ function formatNumber(num: number) {
|
||||
return new Intl.NumberFormat().format(num);
|
||||
}
|
||||
|
||||
const HardwareStatus: FC = () => {
|
||||
const HardwareStatus = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
useLayoutTitle(LL.STATUS_OF(LL.HARDWARE()));
|
||||
@@ -41,7 +36,7 @@ const HardwareStatus: FC = () => {
|
||||
data: data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useRequest(SystemApi.readHardwareStatus, { force: true });
|
||||
} = useAutoRequest(SystemApi.readHardwareStatus, { pollingTime: 2000 });
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
@@ -49,7 +44,6 @@ const HardwareStatus: FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
@@ -199,21 +193,6 @@ const HardwareStatus: FC = () => {
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import type { FC } from 'react';
|
||||
|
||||
import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion';
|
||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import ReportIcon from '@mui/icons-material/Report';
|
||||
import SpeakerNotesOffIcon from '@mui/icons-material/SpeakerNotesOff';
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
@@ -19,8 +15,8 @@ import type { Theme } from '@mui/material';
|
||||
|
||||
import * as MqttApi from 'api/mqtt';
|
||||
|
||||
import { useRequest } from 'alova';
|
||||
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useAutoRequest } from 'alova/client';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { MqttStatusType } from 'types';
|
||||
import { MqttDisconnectReason } from 'types';
|
||||
@@ -57,8 +53,12 @@ export const mqttQueueHighlight = (
|
||||
return theme.palette.warning.main;
|
||||
};
|
||||
|
||||
const MqttStatus: FC = () => {
|
||||
const { data: data, send: loadData, error } = useRequest(MqttApi.readMqttStatus);
|
||||
const MqttStatus = () => {
|
||||
const {
|
||||
data: data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(MqttApi.readMqttStatus, { pollingTime: 5000 });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.STATUS_OF('MQTT'));
|
||||
@@ -148,7 +148,6 @@ const MqttStatus: FC = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
@@ -161,17 +160,6 @@ const MqttStatus: FC = () => {
|
||||
<Divider variant="inset" component="li" />
|
||||
{data.enabled && renderConnectionStatus()}
|
||||
</List>
|
||||
<ButtonRow>
|
||||
<Button
|
||||
startIcon={<RefreshIcon />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={loadData}
|
||||
>
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import DnsIcon from '@mui/icons-material/Dns';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import SwapVerticalCircleIcon from '@mui/icons-material/SwapVerticalCircle';
|
||||
import UpdateIcon from '@mui/icons-material/Update';
|
||||
import {
|
||||
@@ -30,15 +28,19 @@ import type { Theme } from '@mui/material';
|
||||
import * as NTPApi from 'api/ntp';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova';
|
||||
import { useAutoRequest, useRequest } from 'alova/client';
|
||||
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { NTPStatusType, Time } from 'types';
|
||||
import { NTPSyncStatus } from 'types';
|
||||
import { formatDateTime, formatLocalDateTime } from 'utils';
|
||||
|
||||
const NTPStatus: FC = () => {
|
||||
const { data: data, send: loadData, error } = useRequest(NTPApi.readNTPStatus);
|
||||
const NTPStatus = () => {
|
||||
const {
|
||||
data: data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(NTPApi.readNTPStatus, { pollingTime: 5000 });
|
||||
|
||||
const [localTime, setLocalTime] = useState<string>('');
|
||||
const [settingTime, setSettingTime] = useState<boolean>(false);
|
||||
@@ -215,18 +217,6 @@ const NTPStatus: FC = () => {
|
||||
<Divider variant="inset" component="li" />
|
||||
</List>
|
||||
<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) && (
|
||||
<Box flexWrap="nowrap" whiteSpace="nowrap">
|
||||
<ButtonRow>
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import type { FC } from 'react';
|
||||
|
||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||
import DnsIcon from '@mui/icons-material/Dns';
|
||||
import GiteIcon from '@mui/icons-material/Gite';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import RouterIcon from '@mui/icons-material/Router';
|
||||
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
|
||||
import SettingsInputComponentIcon from '@mui/icons-material/SettingsInputComponent';
|
||||
import WifiIcon from '@mui/icons-material/Wifi';
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
@@ -22,8 +18,8 @@ import type { Theme } from '@mui/material';
|
||||
|
||||
import * as NetworkApi from 'api/network';
|
||||
|
||||
import { useRequest } from 'alova';
|
||||
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useAutoRequest } from 'alova/client';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { NetworkStatusType } from 'types';
|
||||
import { NetworkConnectionStatus } from 'types';
|
||||
@@ -84,12 +80,12 @@ const IPs = (status: NetworkStatusType) => {
|
||||
return status.local_ip + ', ' + status.local_ipv6;
|
||||
};
|
||||
|
||||
const NetworkStatus: FC = () => {
|
||||
const NetworkStatus = () => {
|
||||
const {
|
||||
data: data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useRequest(NetworkApi.readNetworkStatus);
|
||||
} = useAutoRequest(NetworkApi.readNetworkStatus, { pollingTime: 5000 });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.STATUS_OF(LL.NETWORK(1)));
|
||||
@@ -125,7 +121,6 @@ const NetworkStatus: FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
@@ -216,21 +211,9 @@ const NetworkStatus: FC = () => {
|
||||
secondary={dnsServers(data)}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</>
|
||||
)}
|
||||
</List>
|
||||
<ButtonRow>
|
||||
<Button
|
||||
startIcon={<RefreshIcon />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={loadData}
|
||||
>
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
|
||||
import { useRequest } from 'alova';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { FormLoader } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
const RESTART_TIMEOUT = 2 * 60 * 1000;
|
||||
const POLL_INTERVAL = 3000;
|
||||
|
||||
const RestartMonitor: FC = () => {
|
||||
const RestartMonitor = () => {
|
||||
const [failed, setFailed] = useState<boolean>(false);
|
||||
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
|
||||
const { LL } = useI18nContext();
|
||||
const { send } = useRequest(SystemApi.readSystemStatus, { force: true });
|
||||
const { send } = useRequest(SystemApi.readSystemStatus);
|
||||
const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT);
|
||||
|
||||
const poll = useRef(async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type FC, useContext, useState } from 'react';
|
||||
import { useContext, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
@@ -10,7 +10,6 @@ import DirectionsBusIcon from '@mui/icons-material/DirectionsBus';
|
||||
import LogoDevIcon from '@mui/icons-material/LogoDev';
|
||||
import MemoryIcon from '@mui/icons-material/Memory';
|
||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import RouterIcon from '@mui/icons-material/Router';
|
||||
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
|
||||
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 {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
@@ -34,7 +32,7 @@ import {
|
||||
import * as SystemApi from 'api/system';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova';
|
||||
import { useAutoRequest, useRequest } from 'alova/client';
|
||||
import { busConnectionStatus } from 'app/main/types';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import ListMenuItem from 'components/layout/ListMenuItem';
|
||||
@@ -44,7 +42,7 @@ import { NTPSyncStatus, NetworkConnectionStatus } from 'types';
|
||||
|
||||
import RestartMonitor from './RestartMonitor';
|
||||
|
||||
const SystemStatus: FC = () => {
|
||||
const SystemStatus = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const navigate = useNavigate();
|
||||
@@ -69,7 +67,10 @@ const SystemStatus: FC = () => {
|
||||
data: data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useRequest(SystemApi.readSystemStatus, { force: true });
|
||||
} = useAutoRequest(SystemApi.readSystemStatus, {
|
||||
initialData: [],
|
||||
pollingTime: 5000
|
||||
});
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -390,17 +391,6 @@ const SystemStatus: FC = () => {
|
||||
</List>
|
||||
|
||||
{renderRestartDialog()}
|
||||
|
||||
<Box mt={2} display="flex" flexWrap="wrap">
|
||||
<Button
|
||||
startIcon={<RefreshIcon />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={loadData}
|
||||
>
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
@@ -17,8 +16,7 @@ import {
|
||||
import * as SystemApi from 'api/system';
|
||||
import { fetchLogES } from 'api/system';
|
||||
|
||||
import { useSSE } from '@alova/scene-react';
|
||||
import { useRequest } from 'alova';
|
||||
import { useRequest, useSSE } from 'alova/client';
|
||||
import {
|
||||
BlockFormControlLabel,
|
||||
BlockNavigation,
|
||||
@@ -72,7 +70,7 @@ const levelLabel = (level: LogLevel) => {
|
||||
}
|
||||
};
|
||||
|
||||
const SystemLog: FC = () => {
|
||||
const SystemLog = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
useLayoutTitle(LL.LOG_OF(LL.SYSTEM(0)));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type ChangeEventHandler, type FC, useContext } from 'react';
|
||||
import { type ChangeEventHandler, useContext } from 'react';
|
||||
|
||||
import { MenuItem, TextField } from '@mui/material';
|
||||
|
||||
@@ -16,7 +16,7 @@ import { I18nContext } from 'i18n/i18n-react';
|
||||
import type { Locales } from 'i18n/i18n-types';
|
||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||
|
||||
const LanguageSelector: FC = () => {
|
||||
const LanguageSelector = () => {
|
||||
const { setLocale, locale } = useContext(I18nContext);
|
||||
|
||||
const onLocaleSelected: ChangeEventHandler<HTMLInputElement> = async ({
|
||||
|
||||
@@ -4,8 +4,7 @@ import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { Box, Toolbar } from '@mui/material';
|
||||
|
||||
import { PROJECT_NAME } from 'api/env';
|
||||
|
||||
import { PROJECT_NAME } from 'env';
|
||||
import type { RequiredChildrenProps } from 'utils';
|
||||
|
||||
import LayoutAppBar from './LayoutAppBar';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { FC } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
@@ -12,7 +11,7 @@ interface LayoutAppBarProps {
|
||||
onToggleDrawer: () => void;
|
||||
}
|
||||
|
||||
const LayoutAppBar: FC<LayoutAppBarProps> = ({ title, onToggleDrawer }) => {
|
||||
const LayoutAppBar = ({ title, onToggleDrawer }: LayoutAppBarProps) => {
|
||||
const pathnames = useLocation()
|
||||
.pathname.split('/')
|
||||
.filter((x) => x);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
|
||||
|
||||
import { PROJECT_NAME } from 'api/env';
|
||||
import { PROJECT_NAME } from 'env';
|
||||
|
||||
import { DRAWER_WIDTH } from './Layout';
|
||||
import LayoutMenu from './LayoutMenu';
|
||||
@@ -23,7 +21,7 @@ interface LayoutDrawerProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const LayoutDrawer: FC<LayoutDrawerProps> = ({ mobileOpen, onClose }) => {
|
||||
const LayoutDrawerProps = ({ mobileOpen, onClose }: LayoutDrawerProps) => {
|
||||
const drawer = (
|
||||
<>
|
||||
<Toolbar disableGutters>
|
||||
@@ -68,4 +66,4 @@ const LayoutDrawer: FC<LayoutDrawerProps> = ({ mobileOpen, onClose }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default LayoutDrawer;
|
||||
export default LayoutDrawerProps;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useContext, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
||||
import AssessmentIcon from '@mui/icons-material/Assessment';
|
||||
@@ -30,7 +29,7 @@ import LayoutMenuItem from 'components/layout/LayoutMenuItem';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
const LayoutMenu: FC = () => {
|
||||
const LayoutMenu = () => {
|
||||
const { me, signOut } = useContext(AuthenticatedContext);
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { FC } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
|
||||
import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
|
||||
@@ -13,12 +12,12 @@ interface LayoutMenuItemProps {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const LayoutMenuItem: FC<LayoutMenuItemProps> = ({
|
||||
const LayoutMenuItem = ({
|
||||
icon: Icon,
|
||||
label,
|
||||
to,
|
||||
disabled
|
||||
}) => {
|
||||
}: LayoutMenuItemProps) => {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const selected = routeMatches(to, pathname);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { FC } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
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,
|
||||
bgcolor,
|
||||
label,
|
||||
text,
|
||||
to,
|
||||
disabled
|
||||
}) => (
|
||||
}: ListMenuItemProps) => (
|
||||
<>
|
||||
{to && !disabled ? (
|
||||
<ListItem
|
||||
|
||||
@@ -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;
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { FC } from 'react';
|
||||
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import { Box, Button, CircularProgress, Typography } from '@mui/material';
|
||||
|
||||
@@ -12,11 +10,11 @@ interface FormLoaderProps {
|
||||
onRetry?: () => void;
|
||||
}
|
||||
|
||||
const FormLoader: FC<FormLoaderProps> = ({
|
||||
const FormLoader = ({
|
||||
errorMessage,
|
||||
onRetry,
|
||||
message = 'Loading…'
|
||||
}) => {
|
||||
}: FormLoaderProps) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
if (errorMessage) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { Box, CircularProgress, Typography } from '@mui/material';
|
||||
import type { Theme } from '@mui/material';
|
||||
|
||||
@@ -9,7 +7,7 @@ interface LoadingSpinnerProps {
|
||||
height?: number | string;
|
||||
}
|
||||
|
||||
const LoadingSpinner: FC<LoadingSpinnerProps> = ({ height = '100%' }) => {
|
||||
const LoadingSpinner = ({ height = '100%' }: LoadingSpinnerProps) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export { default as ApplicationError } from './ApplicationError';
|
||||
export { default as LoadingSpinner } from './LoadingSpinner';
|
||||
export { default as FormLoader } from './FormLoader';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { FC } from 'react';
|
||||
import type { Blocker } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
@@ -12,11 +11,7 @@ import {
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
interface BlockNavigationProps {
|
||||
blocker: Blocker;
|
||||
}
|
||||
|
||||
const BlockNavigation: FC<BlockNavigationProps> = ({ blocker }) => {
|
||||
const BlockNavigation = ({ blocker }: { blocker: Blocker }) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,8 +2,7 @@ import { useContext, useEffect } from 'react';
|
||||
import type { FC } from 'react';
|
||||
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 {
|
||||
AuthenticatedContext,
|
||||
|
||||
@@ -2,8 +2,7 @@ import { useContext } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
import * as AuthenticationApi from 'api/authentication';
|
||||
|
||||
import { fetchLoginRedirect } from 'components/routing/authentication';
|
||||
import { AuthenticationContext } from 'contexts/authentication';
|
||||
import type { RequiredChildrenProps } from 'utils';
|
||||
|
||||
@@ -11,7 +10,7 @@ const RequireUnauthenticated: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
const authenticationContext = useContext(AuthenticationContext);
|
||||
|
||||
return authenticationContext.me ? (
|
||||
<Navigate to={AuthenticationApi.fetchLoginRedirect()} />
|
||||
<Navigate to={fetchLoginRedirect()} />
|
||||
) : (
|
||||
<>{children}</>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import type * as H from 'history';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
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_SEARCH = 'loginSearch';
|
||||
112
interface/src/components/upload/DragNdrop.tsx
Normal file
112
interface/src/components/upload/DragNdrop.tsx
Normal 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;
|
||||
@@ -1,94 +1,82 @@
|
||||
import { Fragment } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import type { DropzoneState } from 'react-dropzone';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
||||
import { Box, Button, LinearProgress, Typography, useTheme } from '@mui/material';
|
||||
import type { Theme } from '@mui/material';
|
||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||
import { Box, Button, LinearProgress, Typography } 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';
|
||||
|
||||
const getBorderColor = (theme: Theme, props: DropzoneState) => {
|
||||
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];
|
||||
};
|
||||
import DragNdrop from './DragNdrop';
|
||||
|
||||
export interface SingleUploadProps {
|
||||
onDrop: (acceptedFiles: File[]) => void;
|
||||
onCancel: () => void;
|
||||
isUploading: boolean;
|
||||
progress: Progress;
|
||||
}
|
||||
const SingleUpload = () => {
|
||||
const [md5, setMd5] = useState<string>();
|
||||
const [restarting, setRestarting] = useState<boolean>(false);
|
||||
const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
|
||||
|
||||
const SingleUpload: FC<SingleUploadProps> = ({
|
||||
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 [file, setFile] = useState<File>();
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const progressText = () => {
|
||||
if (uploading) {
|
||||
return (
|
||||
LL.UPLOADING() +
|
||||
': ' +
|
||||
Math.round((progress.loaded * 100) / progress.total) +
|
||||
'%'
|
||||
);
|
||||
const {
|
||||
loading: isUploading,
|
||||
uploading: progress,
|
||||
send: sendUpload,
|
||||
abort: cancelUpload
|
||||
} = 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 (
|
||||
<Box
|
||||
{...getRootProps({
|
||||
sx: {
|
||||
py: 4,
|
||||
px: 2,
|
||||
borderWidth: 2,
|
||||
borderRadius: 2,
|
||||
borderStyle: 'dashed',
|
||||
color: theme.palette.grey[400],
|
||||
transition: 'border .24s ease-in-out',
|
||||
width: '100%',
|
||||
cursor: uploading ? 'default' : 'pointer',
|
||||
borderColor: getBorderColor(theme, dropzoneState)
|
||||
useEffect(async () => {
|
||||
if (file) {
|
||||
console.log('going to upload file ', file.name);
|
||||
await sendUpload(file).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);
|
||||
}
|
||||
})}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<Box flexDirection="column" display="flex" alignItems="center">
|
||||
<CloudUploadIcon fontSize="large" />
|
||||
<Typography variant="h6">{progressText()}</Typography>
|
||||
{uploading && (
|
||||
<Fragment>
|
||||
});
|
||||
}
|
||||
}, [file]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<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()}</Typography>
|
||||
</Box>
|
||||
|
||||
{isUploading || restartNeeded ? (
|
||||
<>
|
||||
<Box width="100%" p={2}>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
@@ -101,18 +89,44 @@ const SingleUpload: FC<SingleUploadProps> = ({
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{!restartNeeded && (
|
||||
<Button
|
||||
sx={{ ml: 2 }}
|
||||
startIcon={<CancelIcon />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={onCancel}
|
||||
color="error"
|
||||
onClick={cancelUpload}
|
||||
>
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
</Fragment>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<DragNdrop onFileSelected={setFile} />
|
||||
)}
|
||||
|
||||
{md5 && (
|
||||
<Box mt={2}>
|
||||
<Typography variant="body2">{'MD5: ' + md5}</Typography>
|
||||
</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 />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
33
interface/src/components/upload/drag-drop.css
Normal file
33
interface/src/components/upload/drag-drop.css
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,12 @@ import type { FC } from 'react';
|
||||
import { redirect } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import * as AuthenticationApi from 'api/authentication';
|
||||
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 { verifyAuthorization } from 'components/routing/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { Me } from 'types';
|
||||
import type { RequiredChildrenProps } from 'utils';
|
||||
@@ -20,12 +21,9 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
const [initialized, setInitialized] = useState<boolean>(false);
|
||||
const [me, setMe] = useState<Me>();
|
||||
|
||||
const { send: verifyAuthorization } = useRequest(
|
||||
AuthenticationApi.verifyAuthorization(),
|
||||
{
|
||||
const { send: sendVerifyAuthorization } = useRequest(verifyAuthorization(), {
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const signIn = (accessToken: string) => {
|
||||
try {
|
||||
@@ -43,7 +41,6 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
AuthenticationApi.clearAccessToken();
|
||||
setMe(undefined);
|
||||
if (doRedirect) {
|
||||
// navigate('/');
|
||||
redirect('/');
|
||||
}
|
||||
};
|
||||
@@ -51,7 +48,7 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
const refresh = useCallback(async () => {
|
||||
const accessToken = AuthenticationApi.getStorage().getItem(ACCESS_TOKEN);
|
||||
if (accessToken) {
|
||||
await verifyAuthorization()
|
||||
await sendVerifyAuthorization()
|
||||
.then(() => {
|
||||
setMe(AuthenticationApi.decodeMeJWT(accessToken));
|
||||
setInitialized(true);
|
||||
|
||||
@@ -94,8 +94,6 @@ const de: Translation = {
|
||||
APPLICATION: 'Anwendung',
|
||||
CUSTOMIZATIONS: 'Anpassungen',
|
||||
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',
|
||||
CUSTOM: 'Custom',
|
||||
GPIO_OF: '{0} GPIO',
|
||||
@@ -103,7 +101,7 @@ const de: Translation = {
|
||||
TEMPERATURE: 'Temperatur',
|
||||
PHY_TYPE: 'Eth PHY Typ',
|
||||
DISABLED: 'deaktiviert',
|
||||
TX_MODE: 'Tx Modus',
|
||||
TX_MODE: 'EMS Tx Modus',
|
||||
HARDWARE: 'Hardware',
|
||||
EMS_BUS: '{{BUS|EMS BUS}}',
|
||||
GENERAL_OPTIONS: 'Allgemeine Optionen',
|
||||
@@ -128,7 +126,7 @@ const de: Translation = {
|
||||
BOOLEAN_FORMAT_API: 'Boolesches Format API/MQTT',
|
||||
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Parasitäre Stomversorgung',
|
||||
ENABLE_PARASITE: '1-wire Parasitäre Stomversorgung',
|
||||
LOGGING: 'Protokollierung',
|
||||
LOG_HEX: 'EMS-Telegramme hexadezimal protokollieren',
|
||||
ENABLE_SYSLOG: 'Syslog aktivieren',
|
||||
@@ -169,7 +167,7 @@ const de: Translation = {
|
||||
SYSTEM: 'System',
|
||||
LOG_OF: '{0}protokoll',
|
||||
STATUS_OF: '{0} Status',
|
||||
UPLOAD_DOWNLOAD: 'Hoch-/Herunterladen',
|
||||
DOWNLOAD_UPLOAD: 'Herunterladen/Hochladen',
|
||||
VERSION_ON: 'Sie verwenden derzeit',
|
||||
CLOSE: 'Schließen',
|
||||
USE: 'Verwenden Sie',
|
||||
@@ -194,7 +192,6 @@ const de: Translation = {
|
||||
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',
|
||||
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',
|
||||
ERROR: 'Unerwarteter Fehler, bitter versuchen Sie es erneut',
|
||||
TIME_SET: 'Zeit gesetzt',
|
||||
@@ -325,7 +322,7 @@ const de: Translation = {
|
||||
SYSTEM_MEMORY: 'Systemspeicher',
|
||||
APPLICATION_SETTINGS_1: 'Ändern Sie die EMS-ESP-Anwendungseinstellungen',
|
||||
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_1: 'Externe Module aktivieren oder deaktivieren',
|
||||
MODULES_UPDATED: 'Module aktualisiert',
|
||||
@@ -333,7 +330,10 @@ const de: Translation = {
|
||||
MODULES_NONE: 'Keine externen Module erkannt',
|
||||
RENAME: 'Umbenennen',
|
||||
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;
|
||||
|
||||
@@ -94,8 +94,6 @@ const en: Translation = {
|
||||
APPLICATION: 'Application',
|
||||
CUSTOMIZATIONS: 'Customizations',
|
||||
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',
|
||||
CUSTOM: 'Custom',
|
||||
GPIO_OF: '{0} GPIO',
|
||||
@@ -103,7 +101,7 @@ const en: Translation = {
|
||||
TEMPERATURE: 'Temperature',
|
||||
PHY_TYPE: 'Eth PHY Type',
|
||||
DISABLED: 'disabled',
|
||||
TX_MODE: 'Tx Mode',
|
||||
TX_MODE: 'EMS Tx Mode',
|
||||
HARDWARE: 'Hardware',
|
||||
EMS_BUS: '{{BUS|EMS BUS}}',
|
||||
GENERAL_OPTIONS: 'General Options',
|
||||
@@ -124,11 +122,11 @@ const en: Translation = {
|
||||
TRIGGER_TIME: 'Trigger Time',
|
||||
COLD_SHOT_DURATION: 'Cold Shot Duration',
|
||||
FORMATTING_OPTIONS: 'Formatting Options',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Boolean Format Dashboard',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Boolean Format Web',
|
||||
BOOLEAN_FORMAT_API: 'Boolean Format API/MQTT',
|
||||
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Enable parasite power',
|
||||
ENABLE_PARASITE: 'Enable 1-Wire Parasite-Power',
|
||||
LOGGING: 'Logging',
|
||||
LOG_HEX: 'Log EMS telegrams in hexadecimal',
|
||||
ENABLE_SYSLOG: 'Enable Syslog',
|
||||
@@ -169,7 +167,7 @@ const en: Translation = {
|
||||
SYSTEM: 'System',
|
||||
LOG_OF: '{0} Log',
|
||||
STATUS_OF: '{0} Status',
|
||||
UPLOAD_DOWNLOAD: 'Upload/Download',
|
||||
DOWNLOAD_UPLOAD: 'Download/Upload',
|
||||
VERSION_ON: 'You are currently on version',
|
||||
CLOSE: 'Close',
|
||||
USE: 'Use',
|
||||
@@ -194,7 +192,6 @@ const en: Translation = {
|
||||
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',
|
||||
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',
|
||||
ERROR: 'Unexpected Error, please try again',
|
||||
TIME_SET: 'Time set',
|
||||
@@ -252,9 +249,9 @@ const en: Translation = {
|
||||
TIME_ZONE: 'Time Zone',
|
||||
ACCESS_POINT: 'Access Point',
|
||||
AP_PROVIDE: 'Enable Access Point',
|
||||
AP_PROVIDE_TEXT_1: 'always',
|
||||
AP_PROVIDE_TEXT_2: 'when WiFi is disconnected',
|
||||
AP_PROVIDE_TEXT_3: 'never',
|
||||
AP_PROVIDE_TEXT_1: 'Always',
|
||||
AP_PROVIDE_TEXT_2: 'When WiFi is disconnected',
|
||||
AP_PROVIDE_TEXT_3: 'Never',
|
||||
AP_PREFERRED_CHANNEL: 'Preferred Channel',
|
||||
AP_HIDE_SSID: 'Hide SSID',
|
||||
AP_CLIENTS: 'AP Clients',
|
||||
@@ -325,7 +322,7 @@ const en: Translation = {
|
||||
SYSTEM_MEMORY: 'System Memory',
|
||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings',
|
||||
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_1: 'Activate or deactivate external modules',
|
||||
MODULES_UPDATED: 'Modules updated',
|
||||
@@ -333,7 +330,10 @@ const en: Translation = {
|
||||
MODULES_NONE: 'No external modules detected',
|
||||
RENAME: 'Rename',
|
||||
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;
|
||||
|
||||
@@ -94,8 +94,6 @@ const fr: Translation = {
|
||||
APPLICATION: "l'application",
|
||||
CUSTOMIZATIONS: 'Personnalisation',
|
||||
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',
|
||||
CUSTOM: 'Personnalisé',
|
||||
GPIO_OF: 'GPIO {0}',
|
||||
@@ -103,7 +101,7 @@ const fr: Translation = {
|
||||
TEMPERATURE: 'Température',
|
||||
PHY_TYPE: 'Eth PHY Type',
|
||||
DISABLED: 'désactivé',
|
||||
TX_MODE: 'Tx Mode',
|
||||
TX_MODE: 'EMS Tx Mode',
|
||||
HARDWARE: 'Hardware',
|
||||
EMS_BUS: '{{BUS|EMS BUS}}',
|
||||
GENERAL_OPTIONS: 'Options générales',
|
||||
@@ -128,7 +126,7 @@ const fr: Translation = {
|
||||
BOOLEAN_FORMAT_API: 'Format booléen API/MQTT',
|
||||
ENUM_FORMAT: 'Format enum API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Activer la puissance parasite',
|
||||
ENABLE_PARASITE: 'Activer la puissance 1-wire parasite',
|
||||
LOGGING: 'Journal',
|
||||
LOG_HEX: 'Enregistrer les télégrammes EMS en hexadécimal',
|
||||
ENABLE_SYSLOG: 'Activer les logs système',
|
||||
@@ -169,7 +167,7 @@ const fr: Translation = {
|
||||
SYSTEM: 'Système',
|
||||
LOG_OF: '{0} Log',
|
||||
STATUS_OF: 'Statut {0}',
|
||||
UPLOAD_DOWNLOAD: 'Upload/Download',
|
||||
DOWNLOAD_UPLOAD: 'Download/Upload', // TODO translate
|
||||
VERSION_ON: 'You are currently on', // TODO translate
|
||||
CLOSE: 'Fermer',
|
||||
USE: 'Utiliser',
|
||||
@@ -194,7 +192,6 @@ const fr: Translation = {
|
||||
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.",
|
||||
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',
|
||||
ERROR: 'Erreur inattendue, veuillez réessayer',
|
||||
TIME_SET: 'Time set',
|
||||
@@ -325,7 +322,7 @@ const fr: Translation = {
|
||||
SYSTEM_MEMORY: 'System Memory', // TODO translate
|
||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
|
||||
SECURITY_1: 'Add or remove users', // TODO translate
|
||||
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
|
||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
|
||||
MODULES: 'Module', // TODO translate
|
||||
MODULES_1: 'Activer ou désactiver les modules externes', // TODO translate
|
||||
MODULES_UPDATED: 'Modules updated', // TODO translate
|
||||
@@ -333,7 +330,10 @@ const fr: Translation = {
|
||||
MODULES_NONE: 'No external modules detected', // TODO translate
|
||||
RENAME: 'Rename', // TODO translate
|
||||
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;
|
||||
|
||||
@@ -94,8 +94,6 @@ const it: Translation = {
|
||||
APPLICATION: 'Applicazione',
|
||||
CUSTOMIZATIONS: 'Personalizzazione',
|
||||
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',
|
||||
CUSTOM: 'Personalizzazione',
|
||||
GPIO_OF: 'GPIO {0}',
|
||||
@@ -103,7 +101,7 @@ const it: Translation = {
|
||||
TEMPERATURE: 'Temperatura',
|
||||
PHY_TYPE: 'Eth PHY Type',
|
||||
DISABLED: 'disattivato',
|
||||
TX_MODE: 'Modo Tx ',
|
||||
TX_MODE: 'EMS Modo Tx ',
|
||||
HARDWARE: 'Hardware',
|
||||
EMS_BUS: '{{BUS|EMS BUS}}',
|
||||
GENERAL_OPTIONS: 'Opzioni Generali',
|
||||
@@ -128,7 +126,7 @@ const it: Translation = {
|
||||
BOOLEAN_FORMAT_API: 'Formato booleano API/MQTT',
|
||||
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||
INDEX: 'Indice',
|
||||
ENABLE_PARASITE: 'Abilita potenza parassita',
|
||||
ENABLE_PARASITE: 'Abilita potenza 1-wire parassita',
|
||||
LOGGING: 'Registrazione',
|
||||
LOG_HEX: 'Registra telegrammi EMS in esadecimale',
|
||||
ENABLE_SYSLOG: 'Abilita Syslog',
|
||||
@@ -169,7 +167,7 @@ const it: Translation = {
|
||||
SYSTEM: 'Sistema',
|
||||
LOG_OF: 'Registro {0}',
|
||||
STATUS_OF: 'Stato {0}',
|
||||
UPLOAD_DOWNLOAD: 'Caricamento/Scaricamento',
|
||||
DOWNLOAD_UPLOAD: 'Scaricamento/Caricamento',
|
||||
VERSION_ON: 'Attualmente stai eseguendo la versione',
|
||||
CLOSE: 'Chiudere',
|
||||
USE: 'Usa',
|
||||
@@ -194,7 +192,6 @@ const it: Translation = {
|
||||
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',
|
||||
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',
|
||||
ERROR: 'Errore Inaspettato, prego tenta ancora',
|
||||
TIME_SET: 'Imposta Ora',
|
||||
@@ -325,7 +322,7 @@ const it: Translation = {
|
||||
SYSTEM_MEMORY: 'System Memory', // TODO translate
|
||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
|
||||
SECURITY_1: 'Add or remove users', // TODO translate
|
||||
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
|
||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
|
||||
MODULES: 'Module', // TODO translate
|
||||
MODULES_1: 'Attiva o disattiva i moduli esterni', // TODO translate
|
||||
MODULES_UPDATED: 'Modules updated', // TODO translate
|
||||
@@ -333,7 +330,10 @@ const it: Translation = {
|
||||
MODULES_NONE: 'No external modules detected', // TODO translate
|
||||
RENAME: 'Rename', // TODO translate
|
||||
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;
|
||||
|
||||
@@ -94,15 +94,13 @@ const nl: Translation = {
|
||||
APPLICATION: 'Applicatie',
|
||||
CUSTOMIZATIONS: 'Aanpassingen van entiteiten',
|
||||
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',
|
||||
CUSTOM: 'Custom',
|
||||
GPIO_OF: '{0} GPIO',
|
||||
BUTTON: 'Toets',
|
||||
TEMPERATURE: 'Temperatuur',
|
||||
PHY_TYPE: 'Eth PHY Type',
|
||||
TX_MODE: 'Tx Mode',
|
||||
TX_MODE: 'EMS Tx Mode',
|
||||
HARDWARE: 'Hardware',
|
||||
EMS_BUS: '{{BUS|EMS BUS}}',
|
||||
DISABLED: 'Uitgeschakeld',
|
||||
@@ -124,11 +122,11 @@ const nl: Translation = {
|
||||
TRIGGER_TIME: 'Trigger tijd',
|
||||
COLD_SHOT_DURATION: 'Tijd Shot koud water',
|
||||
FORMATTING_OPTIONS: 'Formatteringsopties',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Boolean formaat dashboard',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Boolean formaat web',
|
||||
BOOLEAN_FORMAT_API: 'Boolean formaat API/MQTT',
|
||||
ENUM_FORMAT: 'Enum formaat API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Activeer parasitaire modus',
|
||||
ENABLE_PARASITE: 'Activeer 1-wire parasitaire modus',
|
||||
LOGGING: 'Logging',
|
||||
LOG_HEX: 'Log EMS telegrammen in hexadecimaal',
|
||||
ENABLE_SYSLOG: 'Activeer Syslog',
|
||||
@@ -169,7 +167,7 @@ const nl: Translation = {
|
||||
SYSTEM: 'Systeem',
|
||||
LOG_OF: '{0} Log',
|
||||
STATUS_OF: '{0} Status',
|
||||
UPLOAD_DOWNLOAD: 'Upload/Download',
|
||||
DOWNLOAD_UPLOAD: 'Download/Upload',
|
||||
VERSION_ON: 'U bevindt zich momenteel op versie',
|
||||
CLOSE: 'Sluiten',
|
||||
USE: 'Gebruik',
|
||||
@@ -194,7 +192,6 @@ const nl: Translation = {
|
||||
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',
|
||||
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',
|
||||
ERROR: 'Onverwachte fout, probeer opnieuw',
|
||||
TIME_SET: 'Tijd ingesteld',
|
||||
@@ -325,7 +322,7 @@ const nl: Translation = {
|
||||
SYSTEM_MEMORY: 'System Geheugen',
|
||||
APPLICATION_SETTINGS_1: 'Applicatie-instellingen wijzigen',
|
||||
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_1: 'Externe modules activeren of deactiveren', // TODO translate
|
||||
MODULES_UPDATED: 'Modules geüpdatet',
|
||||
@@ -333,7 +330,10 @@ const nl: Translation = {
|
||||
MODULES_NONE: 'Geen externe modules gedetecteerd',
|
||||
RENAME: 'Hernoemen',
|
||||
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;
|
||||
|
||||
@@ -94,8 +94,6 @@ const no: Translation = {
|
||||
APPLICATION: 'Søknad',
|
||||
CUSTOMIZATIONS: 'Tilpasninger',
|
||||
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',
|
||||
CUSTOM: 'Custom',
|
||||
GPIO_OF: '{0} GPIO',
|
||||
@@ -103,7 +101,7 @@ const no: Translation = {
|
||||
TEMPERATURE: 'Temperatur',
|
||||
PHY_TYPE: 'Eth PHY Type',
|
||||
DISABLED: 'avslått',
|
||||
TX_MODE: 'Tx Mode',
|
||||
TX_MODE: 'EMS Tx Mode',
|
||||
HARDWARE: 'Hardware',
|
||||
EMS_BUS: '{{BUS|EMS BUS}}',
|
||||
GENERAL_OPTIONS: 'Generelle Innstillinger',
|
||||
@@ -124,11 +122,11 @@ const no: Translation = {
|
||||
TRIGGER_TIME: 'Aktiveringstid',
|
||||
COLD_SHOT_DURATION: 'Tid på kaldt vann',
|
||||
FORMATTING_OPTIONS: 'Formatteringsalternativs',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Bool Format Dashboard',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Bool Format Web',
|
||||
BOOLEAN_FORMAT_API: 'Bool Format API/MQTT',
|
||||
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||
INDEX: 'Indeks',
|
||||
ENABLE_PARASITE: 'Aktiver parasitt strømforsyning',
|
||||
ENABLE_PARASITE: 'Aktiver 1-wire parasitt strømforsyning',
|
||||
LOGGING: 'Logging',
|
||||
LOG_HEX: 'Logg EMS telegrammer i hexadesimal',
|
||||
ENABLE_SYSLOG: 'Aktiver Syslog',
|
||||
@@ -169,7 +167,7 @@ const no: Translation = {
|
||||
SYSTEM: 'System',
|
||||
LOG_OF: '{0} Logg',
|
||||
STATUS_OF: '{0} Status',
|
||||
UPLOAD_DOWNLOAD: 'Opp/Nedlasting',
|
||||
DOWNLOAD_UPLOAD: 'Nedlasting/Opp',
|
||||
VERSION_ON: 'You are currently on', // TODO translate
|
||||
CLOSE: 'Steng',
|
||||
USE: 'Bruk',
|
||||
@@ -194,7 +192,6 @@ const no: Translation = {
|
||||
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',
|
||||
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',
|
||||
ERROR: 'Ukjent feil, prøv igjen',
|
||||
TIME_SET: 'Still in tid',
|
||||
@@ -325,7 +322,7 @@ const no: Translation = {
|
||||
SYSTEM_MEMORY: 'System Memory', // TODO translate
|
||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
|
||||
SECURITY_1: 'Add or remove users', // TODO translate
|
||||
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
|
||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
|
||||
MODULES: 'Module', // TODO translate
|
||||
MODULES_1: 'Aktiver eller deaktiver eksterne moduler', // TODO translate
|
||||
MODULES_UPDATED: 'Modules updated', // TODO translate
|
||||
@@ -333,7 +330,10 @@ const no: Translation = {
|
||||
MODULES_NONE: 'No external modules detected', // TODO translate
|
||||
RENAME: 'Rename', // TODO translate
|
||||
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;
|
||||
|
||||
@@ -94,8 +94,6 @@ const pl: BaseTranslation = {
|
||||
APPLICATION: 'Aplikacji',
|
||||
CUSTOMIZATIONS: 'Personalizacja',
|
||||
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',
|
||||
CUSTOM: 'własny',
|
||||
GPIO_OF: 'GPIO {0}',
|
||||
@@ -103,7 +101,7 @@ const pl: BaseTranslation = {
|
||||
TEMPERATURE: '1-Wire®',
|
||||
PHY_TYPE: 'Typ układu ethernetowego (PHY)',
|
||||
DISABLED: '{{wyłączono|brak|}}',
|
||||
TX_MODE: 'Tryb transmisji (Tx)',
|
||||
TX_MODE: 'EMS Tryb transmisji (Tx)',
|
||||
EMS_BUS: '{{magistrali EMS|na magistrali|}}',
|
||||
HARDWARE: 'sprzętowy',
|
||||
GENERAL_OPTIONS: 'Opcje podstawowe',
|
||||
@@ -128,7 +126,7 @@ const pl: BaseTranslation = {
|
||||
BOOLEAN_FORMAT_API: 'Wartości dwustanowe w API/MQTT',
|
||||
ENUM_FORMAT: 'Wartości z listy w API/MQTT',
|
||||
INDEX: 'indeks',
|
||||
ENABLE_PARASITE: 'Aktywuj zasilanie pasożytnicze',
|
||||
ENABLE_PARASITE: 'Aktywuj zasilanie 1-wire pasożytnicze',
|
||||
LOGGING: 'Logowanie',
|
||||
LOG_HEX: 'Loguj telegramy EMS w systemie szesnastkowym (hex)',
|
||||
ENABLE_SYSLOG: 'Aktywuj SysLog',
|
||||
@@ -169,7 +167,7 @@ const pl: BaseTranslation = {
|
||||
SYSTEM: '{{S|s||s}}yste{{m|mu||mowy}}',
|
||||
LOG_OF: 'Log {0}',
|
||||
STATUS_OF: 'Status {0}',
|
||||
UPLOAD_DOWNLOAD: 'Przesyłanie plików',
|
||||
DOWNLOAD_UPLOAD: 'Plików przesyłanie',
|
||||
VERSION_ON: 'Aktualnie używasz',
|
||||
CLOSE: 'Zamknij',
|
||||
USE: 'Aby zaktualizować firmware skorzystaj z funkcji',
|
||||
@@ -194,7 +192,6 @@ const pl: BaseTranslation = {
|
||||
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!',
|
||||
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',
|
||||
ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!',
|
||||
TIME_SET: 'Zegar został ustawiony.',
|
||||
@@ -325,7 +322,7 @@ const pl: BaseTranslation = {
|
||||
SYSTEM_MEMORY: 'Pamięć systemowa',
|
||||
APPLICATION_SETTINGS_1: 'Modyfikacja ustawień aplikacji EMS-ESP',
|
||||
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_1: 'Aktywuj lub dezaktywuj moduły zewnętrzne', // TODO translate
|
||||
MODULES_UPDATED: 'Modules updated', // TODO translate
|
||||
@@ -333,7 +330,10 @@ const pl: BaseTranslation = {
|
||||
MODULES_NONE: 'No external modules detected', // TODO translate
|
||||
RENAME: 'Rename', // TODO translate
|
||||
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;
|
||||
|
||||
@@ -94,8 +94,6 @@ const sk: Translation = {
|
||||
APPLICATION: 'Aplikácie',
|
||||
CUSTOMIZATIONS: 'Prispôsobenia',
|
||||
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',
|
||||
CUSTOM: 'Vlastné',
|
||||
GPIO_OF: '{0} GPIO',
|
||||
@@ -103,7 +101,7 @@ const sk: Translation = {
|
||||
TEMPERATURE: 'Teplota',
|
||||
PHY_TYPE: 'Eth PHY Typ',
|
||||
DISABLED: 'zakázané',
|
||||
TX_MODE: 'Tx režim',
|
||||
TX_MODE: 'EMS Tx režim',
|
||||
HARDWARE: 'Hardware',
|
||||
EMS_BUS: '{{BUS|EMS BUS}}',
|
||||
GENERAL_OPTIONS: 'Všeobecné možnosti',
|
||||
@@ -124,11 +122,11 @@ const sk: Translation = {
|
||||
TRIGGER_TIME: 'Čas spustenia',
|
||||
COLD_SHOT_DURATION: 'Trvanie studeného záberu',
|
||||
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',
|
||||
ENUM_FORMAT: 'Enum formát API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Povoliť parazité napájanie DS18B20',
|
||||
ENABLE_PARASITE: 'Povoliť 1-wire parazité napájanie DS18B20',
|
||||
LOGGING: 'Logovanie',
|
||||
LOG_HEX: 'Záznam telegramov EMS v hexadecimálnej sústave',
|
||||
ENABLE_SYSLOG: 'Povoliť Syslog',
|
||||
@@ -169,7 +167,7 @@ const sk: Translation = {
|
||||
SYSTEM: 'Systém',
|
||||
LOG_OF: '{0} Log',
|
||||
STATUS_OF: '{0} Stav',
|
||||
UPLOAD_DOWNLOAD: 'Nahrať/Stiahnuť',
|
||||
DOWNLOAD_UPLOAD: 'Stiahnuť/Nahrať',
|
||||
VERSION_ON: 'Momentálne nainštalovaná verzia: ',
|
||||
CLOSE: 'Zatvoriť',
|
||||
USE: 'Použiť',
|
||||
@@ -194,7 +192,6 @@ const sk: Translation = {
|
||||
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.',
|
||||
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',
|
||||
ERROR: 'Neočakávaná chyba, prosím skúste to znova',
|
||||
TIME_SET: 'Nastavený čas',
|
||||
@@ -325,7 +322,7 @@ const sk: Translation = {
|
||||
SYSTEM_MEMORY: 'System Memory', // TODO translate
|
||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
|
||||
SECURITY_1: 'Add or remove users', // TODO translate
|
||||
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
|
||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
|
||||
MODULES: 'Module', // TODO translate
|
||||
MODULES_1: 'Aktivujte alebo deaktivujte externé moduly', // TODO translate
|
||||
MODULES_UPDATED: 'Modules updated', // TODO translate
|
||||
@@ -333,7 +330,10 @@ const sk: Translation = {
|
||||
MODULES_NONE: 'No external modules detected', // TODO translate
|
||||
RENAME: 'Rename', // TODO translate
|
||||
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;
|
||||
|
||||
@@ -94,8 +94,6 @@ const sv: Translation = {
|
||||
APPLICATION: 'Apliká',
|
||||
CUSTOMIZATIONS: 'Anpassningr',
|
||||
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',
|
||||
CUSTOM: 'Anpassa',
|
||||
GPIO_OF: '{0} GPIO',
|
||||
@@ -103,7 +101,7 @@ const sv: Translation = {
|
||||
TEMPERATURE: 'Temperatur',
|
||||
PHY_TYPE: 'Eth PHY-typ',
|
||||
DISABLED: 'inaktiverad',
|
||||
TX_MODE: 'Tx-läge',
|
||||
TX_MODE: 'EMS Tx-läge',
|
||||
HARDWARE: 'Hårdvara',
|
||||
EMS_BUS: '{{BUSS|EMS-BUSS}}',
|
||||
GENERAL_OPTIONS: 'Allmänna Inställningar',
|
||||
@@ -128,7 +126,7 @@ const sv: Translation = {
|
||||
BOOLEAN_FORMAT_API: 'Bool-format API/MQTT',
|
||||
ENUM_FORMAT: 'Enum-format API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Aktivera parasitström',
|
||||
ENABLE_PARASITE: 'Aktivera 1-wire parasitström',
|
||||
LOGGING: 'Loggning',
|
||||
LOG_HEX: 'Logga EMS-telegram i hexadecimal',
|
||||
ENABLE_SYSLOG: 'Aktivera Syslog',
|
||||
@@ -169,7 +167,7 @@ const sv: Translation = {
|
||||
SYSTEM: 'System',
|
||||
LOG_OF: '{0} Logg',
|
||||
STATUS_OF: '{0} Status',
|
||||
UPLOAD_DOWNLOAD: 'Upp/Nedladdning',
|
||||
DOWNLOAD_UPLOAD: 'Nedladdning/Upp',
|
||||
VERSION_ON: 'You are currently on', // TODO translate
|
||||
CLOSE: 'Stäng',
|
||||
USE: 'Använd',
|
||||
@@ -194,7 +192,6 @@ const sv: Translation = {
|
||||
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',
|
||||
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',
|
||||
ERROR: 'Okänt Fel, var god försök igen',
|
||||
TIME_SET: 'Ställ in tid',
|
||||
@@ -325,7 +322,7 @@ const sv: Translation = {
|
||||
SYSTEM_MEMORY: 'System Memory', // TODO translate
|
||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
|
||||
SECURITY_1: 'Add or remove users', // TODO translate
|
||||
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
|
||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
|
||||
MODULES: 'Module', // TODO translate
|
||||
MODULES_1: 'Aktivera eller avaktivera externa moduler', // TODO translate
|
||||
MODULES_UPDATED: 'Modules updated', // TODO translate
|
||||
@@ -333,7 +330,10 @@ const sv: Translation = {
|
||||
MODULES_NONE: 'No external modules detected', // TODO translate
|
||||
RENAME: 'Rename', // TODO translate
|
||||
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;
|
||||
|
||||
@@ -94,8 +94,6 @@ const tr: Translation = {
|
||||
APPLICATION: 'Uygulama',
|
||||
CUSTOMIZATIONS: 'Özelleştirme',
|
||||
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',
|
||||
CUSTOM: 'Özel',
|
||||
GPIO_OF: '{0} GPIO',
|
||||
@@ -103,7 +101,7 @@ const tr: Translation = {
|
||||
TEMPERATURE: 'Sıcaklık',
|
||||
PHY_TYPE: 'Eth PHY Tipi',
|
||||
DISABLED: 'devre dışı',
|
||||
TX_MODE: 'Tx Modu',
|
||||
TX_MODE: 'EMS Tx Modu',
|
||||
HARDWARE: 'Donanım',
|
||||
EMS_BUS: '{{HAT|EMS HATTI}}',
|
||||
GENERAL_OPTIONS: 'Genel Seçenekler',
|
||||
@@ -128,7 +126,7 @@ const tr: Translation = {
|
||||
BOOLEAN_FORMAT_API: 'Boolean Biçimleme API/MQTT',
|
||||
ENUM_FORMAT: 'Enum Biçimleme API/MQTT',
|
||||
INDEX: 'İndeks',
|
||||
ENABLE_PARASITE: 'Parazit gücü devreye al',
|
||||
ENABLE_PARASITE: '1-wire parazit gücü devreye al',
|
||||
LOGGING: 'Kayıt ediliyor',
|
||||
LOG_HEX: 'EMS telegramlarını hexadecimal olarak kayıt et',
|
||||
ENABLE_SYSLOG: 'Sistem Kaydını Devreye Al',
|
||||
@@ -169,7 +167,7 @@ const tr: Translation = {
|
||||
SYSTEM: 'Sistem',
|
||||
LOG_OF: '{0} Kaydı',
|
||||
STATUS_OF: '{0} Durumu',
|
||||
UPLOAD_DOWNLOAD: 'Yükleme/İndirme',
|
||||
DOWNLOAD_UPLOAD: 'İndirme/Yükleme',
|
||||
VERSION_ON: 'You are currently on', // TODO translate
|
||||
CLOSE: 'Kapat',
|
||||
USE: 'KUllan',
|
||||
@@ -194,7 +192,6 @@ const tr: Translation = {
|
||||
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',
|
||||
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',
|
||||
ERROR: 'Beklenemedik hata, lütfen tekrar deneyin.',
|
||||
TIME_SET: 'Zaman ayarı',
|
||||
@@ -325,7 +322,7 @@ const tr: Translation = {
|
||||
SYSTEM_MEMORY: 'System Memory', // TODO translate
|
||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
|
||||
SECURITY_1: 'Add or remove users', // TODO translate
|
||||
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
|
||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
|
||||
MODULES: 'Module', // TODO translate
|
||||
MODULES_1: 'Harici modülleri etkinleştirin veya devre dışı bırakın', // TODO translate
|
||||
MODULES_UPDATED: 'Modules updated', // TODO translate
|
||||
@@ -333,7 +330,10 @@ const tr: Translation = {
|
||||
MODULES_NONE: 'No external modules detected', // TODO translate
|
||||
RENAME: 'Rename', // 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;
|
||||
|
||||
@@ -2,51 +2,42 @@ import { useState } from 'react';
|
||||
import { useBlocker } from 'react-router-dom';
|
||||
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';
|
||||
|
||||
export interface RestRequestOptions<D> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
read: () => Method<any, any, any, any, any, any, any>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
update: (value: D) => Method<any, any, any, any, any, any, any>;
|
||||
read: () => Method<AlovaGenerics>;
|
||||
update: (value: D) => Method<AlovaGenerics>;
|
||||
}
|
||||
|
||||
export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
|
||||
const [origData, setOrigData] = useState<D>();
|
||||
|
||||
const [dirtyFlags, setDirtyFlags] = useState<string[]>([]);
|
||||
const blocker = useBlocker(dirtyFlags.length !== 0);
|
||||
|
||||
const {
|
||||
data,
|
||||
send: readData,
|
||||
update: updateData,
|
||||
onComplete: onReadComplete
|
||||
} = useRequest(read());
|
||||
update: updateData
|
||||
} = useRequest(read()).onComplete((event) => {
|
||||
setOrigData(event.data as D);
|
||||
});
|
||||
|
||||
const {
|
||||
loading: saving,
|
||||
send: writeData,
|
||||
onSuccess: onWriteSuccess
|
||||
} = useRequest((newData: D) => update(newData), { immediate: false });
|
||||
|
||||
const updateDataValue = (new_data: D) => {
|
||||
updateData({ data: new_data });
|
||||
};
|
||||
|
||||
onWriteSuccess(() => {
|
||||
const { loading: saving, send: writeData } = useRequest(
|
||||
(newData: D) => update(newData),
|
||||
{ immediate: false }
|
||||
).onSuccess(() => {
|
||||
toast.success(LL.UPDATED_OF(LL.SETTINGS(1)));
|
||||
setDirtyFlags([]);
|
||||
});
|
||||
|
||||
onReadComplete((event) => {
|
||||
setOrigData(event.data as D);
|
||||
});
|
||||
const updateDataValue = (new_data: D) => {
|
||||
updateData({ data: new_data });
|
||||
};
|
||||
|
||||
const loadData = async () => {
|
||||
setDirtyFlags([]);
|
||||
@@ -74,7 +65,6 @@ export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
loadData,
|
||||
saveData,
|
||||
|
||||
@@ -12,7 +12,7 @@ export const validate = <T extends object>(
|
||||
options ? options : {},
|
||||
(errors, fieldErrors) => {
|
||||
if (errors) {
|
||||
reject(fieldErrors);
|
||||
reject(fieldErrors as Error);
|
||||
} else {
|
||||
resolve(source as T);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import { defineConfig } from 'vite';
|
||||
import viteImagemin from 'vite-plugin-imagemin';
|
||||
import viteTsconfigPaths from 'vite-tsconfig-paths';
|
||||
|
||||
import mockServer from '../mock-api/mockServer.js';
|
||||
|
||||
export default defineConfig(({ command, mode }) => {
|
||||
if (command === 'serve') {
|
||||
console.log('Preparing for standalone build with server, mode=' + mode);
|
||||
return {
|
||||
plugins: [preact(), viteTsconfigPaths()],
|
||||
plugins: [preact(), viteTsconfigPaths(), mockServer()],
|
||||
server: {
|
||||
open: true,
|
||||
port: mode == 'production' ? 4173 : 3000,
|
||||
@@ -18,12 +20,6 @@ export default defineConfig(({ command, mode }) => {
|
||||
changeOrigin: true,
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
1379
interface/yarn.lock
1379
interface/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -75,6 +75,7 @@ void APSettingsService::manageAP() {
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
@@ -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
123
mock-api/mockServer.js
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -6,15 +6,16 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"mock-rest": "bun --watch rest_server.ts",
|
||||
"mock-es": "bun --watch es_server.ts",
|
||||
"mock-upload": "bun --watch upload_server.ts"
|
||||
"format": "prettier -l -w '**/*.{ts,tsx,js,css,json,md}'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@msgpack/msgpack": "^2.8.0",
|
||||
"@types/multer": "^1.4.11",
|
||||
"express": "^4.19.2",
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"eslint": "^9.9.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"formidable": "^3.5.1",
|
||||
"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
@@ -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}/`));
|
||||
1787
mock-api/yarn.lock
1787
mock-api/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,7 @@
|
||||
; default_envs = esp32_4M
|
||||
; default_envs = esp32_16M
|
||||
default_envs = lolin_s3
|
||||
; default_envs = lolin_c3_mini
|
||||
; default_envs = native
|
||||
; default_envs = debug
|
||||
; default_envs = custom
|
||||
@@ -26,11 +27,11 @@ default_envs = lolin_s3
|
||||
[env]
|
||||
; upload settings
|
||||
; for USB
|
||||
upload_protocol = esptool
|
||||
upload_port = /dev/ttyUSB*
|
||||
; upload_protocol = esptool
|
||||
; upload_port = /dev/ttyUSB*
|
||||
; for OTA add scripts/upload.py to extra_scripts
|
||||
; upload_protocol = custom
|
||||
custom_emsesp_ip = 10.10.10.173
|
||||
upload_protocol = custom
|
||||
custom_emsesp_ip = 10.10.10.175
|
||||
; custom_emsesp_ip = ems-esp.local
|
||||
custom_username = admin
|
||||
custom_password = admin
|
||||
@@ -42,59 +43,25 @@ custom_password = admin
|
||||
[env:native]
|
||||
extra_scripts =
|
||||
; pre:scripts/refresh_module_library_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
|
||||
post:scripts/run_native.py
|
||||
|
||||
[env:lolin_s3]
|
||||
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/upload.py
|
||||
scripts/upload.py
|
||||
|
||||
[env:esp32_16M]
|
||||
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/upload.py
|
||||
|
||||
[env:custom]
|
||||
; 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
|
||||
[env:lolin_c3_mini]
|
||||
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
|
||||
build_unflags = ${common.unbuild_flags}
|
||||
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"'
|
||||
scripts/upload.py
|
||||
|
||||
; pio run -e debug
|
||||
; or from Visual Studio Code do PIO -> Project Tasks -> debug -> General -> Upload and Monitor
|
||||
|
||||
@@ -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.
|
||||
;
|
||||
; 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 on Windows, it needs winsock for the console input so:
|
||||
@@ -245,7 +247,6 @@ build_src_flags =
|
||||
-I./lib/espMqttClient/src/Transport
|
||||
build_src_filter =
|
||||
+<*>
|
||||
-<.git/>
|
||||
+<../lib_standalone>
|
||||
+<../lib/uuid-common>
|
||||
+<../lib/uuid-console>
|
||||
|
||||
@@ -1,11 +1,60 @@
|
||||
import shutil
|
||||
import re
|
||||
import os
|
||||
|
||||
Import("env")
|
||||
|
||||
def run_native():
|
||||
print("Running native...")
|
||||
os.system("pio run -e native")
|
||||
OUTPUT_DIR = "build{}".format(os.path.sep)
|
||||
|
||||
if not (env.IsCleanTarget()):
|
||||
run_native()
|
||||
def move_file(source, target, env):
|
||||
|
||||
# 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])
|
||||
|
||||
@@ -418,7 +418,7 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
|
||||
LOG_WARNING(error);
|
||||
} else {
|
||||
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);
|
||||
} else {
|
||||
if (id > 0) {
|
||||
|
||||
@@ -691,11 +691,16 @@ std::string EMSESPShell::context_text() {
|
||||
|
||||
// when in su (admin) show # as the prompt suffix
|
||||
std::string EMSESPShell::prompt_suffix() {
|
||||
#ifndef EMSESP_UNITY
|
||||
if (has_flags(CommandFlags::ADMIN)) {
|
||||
return std::string{'#'};
|
||||
} else {
|
||||
return std::string{'$'};
|
||||
}
|
||||
#else
|
||||
// don't bother with prompt suffix if we're testing Unity output
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
void EMSESPShell::end_of_transmission() {
|
||||
|
||||
@@ -1553,8 +1553,6 @@ void EMSESP::start() {
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
shell_->add_flags(CommandFlags::ADMIN); // always start in su/admin mode when running tests
|
||||
#endif
|
||||
#else
|
||||
#warning "Shell is disabled when running Unity tests."
|
||||
#endif
|
||||
|
||||
// start the file system
|
||||
|
||||
@@ -1398,7 +1398,12 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
||||
|
||||
// System
|
||||
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;
|
||||
#endif
|
||||
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
|
||||
node["uptimeSec"] = uuid::get_uptime_sec();
|
||||
#ifndef EMSESP_STANDALONE
|
||||
|
||||
@@ -1109,6 +1109,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
Serial.println();
|
||||
Serial.printf("%s**** Testing bad urls ****\n%s", COLOR_RED, COLOR_RESET);
|
||||
|
||||
request.method(HTTP_GET);
|
||||
|
||||
request.url("/api/boiler2");
|
||||
EMSESP::webAPIService.webAPIService(&request);
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "3.7.0-dev.30"
|
||||
#define EMSESP_APP_VERSION "3.7.0-dev.31"
|
||||
|
||||
@@ -134,7 +134,7 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
const char * api_data = output["api_data"];
|
||||
if (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
|
||||
storeResponse(output);
|
||||
#endif
|
||||
@@ -161,7 +161,7 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
request->send(response);
|
||||
api_count_++;
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
#if defined(EMSESP_UNITY)
|
||||
// store the result so we can test with Unity later
|
||||
storeResponse(output);
|
||||
#endif
|
||||
@@ -229,7 +229,7 @@ void WebAPIService::getEntities(AsyncWebServerRequest * request) {
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
#if defined(EMSESP_UNITY)
|
||||
// store the result so we can test with Unity later
|
||||
static JsonDocument storeResponseDoc_;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user