This commit is contained in:
proddy
2025-03-22 10:32:03 +01:00
parent e418b7d8e7
commit eaa277fef0
281 changed files with 15297 additions and 21851 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,3 @@
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.5.3.cjs
yarnPath: .yarn/releases/yarn-4.7.0.cjs

View File

@@ -1,6 +1,6 @@
{
"name": "EMS-ESP",
"version": "3.7.1",
"version": "3.7.2",
"description": "EMS-ESP WebUI",
"homepage": "https://emsesp.org",
"author": "proddy, emsesp.org",
@@ -21,46 +21,46 @@
"lint": "eslint . --fix"
},
"dependencies": {
"@alova/adapter-xhr": "2.0.10",
"@emotion/react": "^11.13.5",
"@emotion/styled": "^11.13.5",
"@mui/icons-material": "^6.1.9",
"@mui/material": "^6.1.9",
"@table-library/react-table-library": "4.1.7",
"alova": "3.2.5",
"@alova/adapter-xhr": "2.1.1",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^6.4.8",
"@mui/material": "^6.4.8",
"@table-library/react-table-library": "4.1.12",
"alova": "3.2.10",
"async-validator": "^4.2.5",
"jwt-decode": "^4.0.0",
"mime-types": "^2.1.35",
"preact": "^10.25.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
"react-router": "^7.0.1",
"react-toastify": "^10.0.6",
"preact": "^10.26.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-icons": "^5.5.0",
"react-router": "^7.4.0",
"react-toastify": "^11.0.5",
"typesafe-i18n": "^5.26.2",
"typescript": "^5.7.2"
"typescript": "^5.8.2"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@eslint/js": "^9.15.0",
"@babel/core": "^7.26.10",
"@eslint/js": "^9.23.0",
"@preact/compat": "^18.3.1",
"@preact/preset-vite": "^2.9.2",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@preact/preset-vite": "^2.10.1",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/formidable": "^3",
"@types/node": "^22.10.1",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"concurrently": "^9.1.0",
"eslint": "^9.15.0",
"eslint-config-prettier": "^9.1.0",
"@types/node": "^22.13.11",
"@types/react": "^19.0.12",
"@types/react-dom": "^19.0.4",
"concurrently": "^9.1.2",
"eslint": "^9.23.0",
"eslint-config-prettier": "^10.1.1",
"formidable": "^3.5.2",
"prettier": "^3.4.1",
"rollup-plugin-visualizer": "^5.12.0",
"terser": "^5.36.0",
"typescript-eslint": "8.16.0",
"vite": "^6.0.1",
"prettier": "^3.5.3",
"rollup-plugin-visualizer": "^5.14.0",
"terser": "^5.39.0",
"typescript-eslint": "8.27.0",
"vite": "^6.2.2",
"vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^5.1.3"
"vite-tsconfig-paths": "^5.1.4"
},
"packageManager": "yarn@4.5.3"
"packageManager": "yarn@4.7.0"
}

View File

@@ -12,7 +12,7 @@ import zlib from 'zlib';
const ARDUINO_INCLUDES = '#include <Arduino.h>\n\n';
const INDENT = ' ';
const outputPath = '../lib/framework/WWWData.h';
const outputPath = '../src/ESP32React/WWWData.h';
const sourcePath = './dist';
const bytesPerLine = 20;
var totalSize = 0;

View File

@@ -13,8 +13,9 @@
local('Roboto'),
local('Roboto-Regular'),
url(../fonts/re.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0104-0107, U+0118-0119, U+011E-011F, U+0130-0131,
U+0141-0144, U+0152-0153, U+015A-015B, U+015E-015F, U+0179-017C, U+02BB-02BC,
U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
unicode-range:
U+0000-00FF, U+0104-0107, U+0118-0119, U+011E-011F, U+0130-0131, U+0141-0144,
U+0152-0153, U+015A-015B, U+015E-015F, U+0179-017C, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
U+FEFF, U+FFFD;
}

View File

@@ -1,27 +1,44 @@
import { useEffect, useState } from 'react';
import { Slide, ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.min.css';
import { ToastContainer, Zoom } from 'react-toastify';
import AppRouting from 'AppRouting';
import CustomTheme from 'CustomTheme';
import TypesafeI18n from 'i18n/i18n-react';
import { detectLocale } from 'i18n/i18n-util';
import type { Locales } from 'i18n/i18n-types';
import { loadLocaleAsync } from 'i18n/i18n-util.async';
import { localStorageDetector } from 'typesafe-i18n/detectors';
import { detectLocale, navigatorDetector } from 'typesafe-i18n/detectors';
const detectedLocale = detectLocale(localStorageDetector);
const availableLocales = [
'de',
'en',
'it',
'fr',
'nl',
'no',
'pl',
'sk',
'sv',
'tr',
'cz'
];
const App = () => {
const [wasLoaded, setWasLoaded] = useState(false);
const [locale, setLocale] = useState<Locales>('en');
useEffect(() => {
void loadLocaleAsync(detectedLocale).then(() => setWasLoaded(true));
// determine locale, take from session if set other default to browser language
const browserLocale = detectLocale('en', availableLocales, navigatorDetector);
const newLocale = (localStorage.getItem('lang') || browserLocale) as Locales;
localStorage.setItem('lang', newLocale);
setLocale(newLocale);
void loadLocaleAsync(newLocale).then(() => setWasLoaded(true));
}, []);
if (!wasLoaded) return null;
return (
<TypesafeI18n locale={detectedLocale}>
<TypesafeI18n locale={locale}>
<CustomTheme>
<AppRouting />
<ToastContainer
@@ -29,14 +46,17 @@ const App = () => {
autoClose={3000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick={true}
closeOnClick
rtl={false}
pauseOnFocusLoss={false}
pauseOnFocusLoss
draggable={false}
pauseOnHover={false}
transition={Slide}
transition={Zoom}
closeButton={false}
theme="light"
theme="dark"
toastStyle={{
border: '1px solid #177ac9'
}}
/>
</CustomTheme>
</TypesafeI18n>

View File

@@ -15,7 +15,6 @@ 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 Version from 'app/settings/Version';
import Network from 'app/settings/network/Network';
import Security from 'app/settings/security/Security';
import APStatus from 'app/status/APStatus';
@@ -26,6 +25,7 @@ import NTPStatus from 'app/status/NTPStatus';
import NetworkStatus from 'app/status/NetworkStatus';
import Status from 'app/status/Status';
import SystemLog from 'app/status/SystemLog';
import Version from 'app/status/Version';
import { Layout } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
@@ -48,17 +48,17 @@ const AuthenticatedRouting = () => {
<Route path="/status/ntp" element={<NTPStatus />} />
<Route path="/status/ap" element={<APStatus />} />
<Route path="/status/network" element={<NetworkStatus />} />
<Route path="/status/version" element={<Version />} />
{me.admin && (
<>
<Route path="/settings" element={<Settings />} />
<Route path="/settings/version" element={<Version />} />
<Route path="/settings/application" element={<ApplicationSettings />} />
<Route path="/settings/mqtt" element={<MqttSettings />} />
<Route path="/settings/ntp" element={<NTPSettings />} />
<Route path="/settings/ap" element={<APSettings />} />
<Route path="/settings/modules" element={<Modules />} />
<Route path="/settings/upload" element={<DownloadUpload />} />
<Route path="/settings/downloadUpload" element={<DownloadUpload />} />
<Route path="/settings/network/*" element={<Network />} />
<Route path="/settings/security/*" element={<Security />} />

View File

@@ -1,11 +1,7 @@
import type { FC } from 'react';
import { CssBaseline } from '@mui/material';
import {
ThemeProvider,
createTheme,
responsiveFontSizes
} from '@mui/material/styles';
import { CssBaseline, ThemeProvider, responsiveFontSizes } from '@mui/material';
import { createTheme } from '@mui/material/styles';
import type { RequiredChildrenProps } from 'utils';

View File

@@ -5,7 +5,7 @@ import type {
Action,
Activity,
CoreData,
DashboardItem,
DashboardData,
DeviceData,
DeviceEntity,
Entities,
@@ -22,7 +22,7 @@ import type {
// Dashboard
export const readDashboard = () =>
alovaInstance.Get<DashboardItem[]>('/rest/dashboardData', {
alovaInstance.Get<DashboardData>('/rest/dashboardData', {
responseType: 'arraybuffer' // uses msgpack
});

View File

@@ -22,7 +22,7 @@ export const alovaInstance = createAlova({
method.config.headers.Authorization =
'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
}
// for simulating vrey slow networks
// for simulating very slow networks
// return new Promise((resolve) => {
// const random = 3000 + Math.random() * 2000;
// setTimeout(resolve, Math.floor(random));

View File

@@ -2,7 +2,7 @@ import type { LogSettings, SystemStatus } from 'types';
import { alovaInstance, alovaInstanceGH } from './endpoints';
// systemStatus - also used to ping in Restart monitor for pinging
// systemStatus - also used to ping in System Monitor for pinging
export const readSystemStatus = () =>
alovaInstance.Get<SystemStatus>('/rest/systemStatus');
@@ -14,16 +14,25 @@ export const updateLogSettings = (data: LogSettings) =>
export const fetchLogES = () => alovaInstance.Get('/es/log');
// Get versions from GitHub
// cache for 10 minutes to stop getting the IP blocked by GitHub
export const getStableVersion = () =>
alovaInstanceGH.Get('latest', {
transform(response: { data: { name: string } }) {
return response.data.name.substring(1);
cacheFor: 60 * 10 * 1000,
transform(response: { data: { name: string; published_at: string } }) {
return {
name: response.data.name.substring(1),
published_at: response.data.published_at
};
}
});
export const getDevVersion = () =>
alovaInstanceGH.Get('tags/latest', {
transform(response: { data: { name: string } }) {
return response.data.name.split(/\s+/).splice(-1)[0].substring(1);
cacheFor: 60 * 10 * 1000,
transform(response: { data: { name: string; published_at: string } }) {
return {
name: response.data.name.split(/\s+/).splice(-1)[0].substring(1),
published_at: response.data.published_at
};
}
});

View File

@@ -57,7 +57,7 @@ const CustomEntities = () => {
if (!dialogOpen && !numChanges) {
void fetchEntities();
}
}, 3000);
});
const { send: writeEntities } = useRequest(
(data: Entities) => writeCustomEntities(data),
@@ -83,7 +83,7 @@ const CustomEntities = () => {
const entity_theme = useTheme({
Table: `
--data-table-library_grid-template-columns: repeat(1, minmax(60px, 1fr)) minmax(80px, auto) 80px 80px 80px 90px;
--data-table-library_grid-template-columns: repeat(1, minmax(60px, 1fr)) minmax(80px, auto) 80px 80px 80px 120px;
`,
BaseRow: `
font-size: 14px;
@@ -195,6 +195,25 @@ const CustomEntities = () => {
});
};
const onDialogDup = (item: EntityItem) => {
setCreating(true);
setSelectedEntityItem({
id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100),
name: item.name + '_',
ram: item.ram,
device_id: item.device_id,
type_id: item.type_id,
offset: item.offset,
factor: item.factor,
uom: item.uom,
value_type: item.value_type,
writeable: item.writeable,
deleted: false,
value: item.value
});
setDialogOpen(true);
};
const addEntityItem = () => {
setCreating(true);
setSelectedEntityItem({
@@ -220,7 +239,7 @@ const CustomEntities = () => {
: typeof value === 'number'
? new Intl.NumberFormat().format(value) +
(uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom])
: (value as string);
: (value as string) + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom]);
}
function showHex(value: number, digit: number) {
@@ -296,6 +315,7 @@ const CustomEntities = () => {
creating={creating}
onClose={onDialogClose}
onSave={onDialogSave}
onDup={onDialogDup}
selectedItem={selectedEntityItem}
validator={entityItemValidation(entities, selectedEntityItem)}
/>

View File

@@ -12,11 +12,11 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid2 as Grid,
InputAdornment,
MenuItem,
TextField
} from '@mui/material';
import Grid from '@mui/material/Grid2';
import { dialogStyle } from 'CustomTheme';
import type Schema from 'async-validator';
@@ -34,6 +34,7 @@ interface CustomEntitiesDialogProps {
creating: boolean;
onClose: () => void;
onSave: (ei: EntityItem) => void;
onDup: (ei: EntityItem) => void;
selectedItem: EntityItem;
validator: Schema;
}
@@ -43,6 +44,7 @@ const CustomEntitiesDialog = ({
creating,
onClose,
onSave,
onDup,
selectedItem,
validator
}: CustomEntitiesDialogProps) => {
@@ -59,7 +61,11 @@ const CustomEntitiesDialog = ({
setEditItem({
...selectedItem,
device_id: selectedItem.device_id.toString(16).toUpperCase(),
type_id: selectedItem.type_id.toString(16).toUpperCase()
type_id: selectedItem.type_id.toString(16).toUpperCase(),
factor:
selectedItem.value_type === DeviceValueType.BOOL
? selectedItem.factor.toString(16).toUpperCase()
: selectedItem.factor
});
}
}, [open, selectedItem]);
@@ -80,6 +86,12 @@ const CustomEntitiesDialog = ({
if (typeof editItem.type_id === 'string') {
editItem.type_id = parseInt(editItem.type_id, 16);
}
if (
editItem.value_type === DeviceValueType.BOOL &&
typeof editItem.factor === 'string'
) {
editItem.factor = parseInt(editItem.factor, 16);
}
onSave(editItem);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
@@ -91,6 +103,10 @@ const CustomEntitiesDialog = ({
onSave(editItem);
};
const dup = () => {
onDup(editItem);
};
return (
<Dialog sx={dialogStyle} open={open} onClose={handleClose}>
<DialogTitle>
@@ -128,18 +144,36 @@ const CustomEntitiesDialog = ({
</TextField>
</Grid>
{editItem.ram === 1 && (
<Grid>
<TextField
name="value"
label={LL.DEFAULT(0) + ' ' + LL.VALUE(0)}
type="string"
value={editItem.value as string}
variant="outlined"
onChange={updateFormValue}
fullWidth
margin="normal"
/>
</Grid>
<>
<Grid>
<TextField
name="value"
label={LL.DEFAULT(0) + ' ' + LL.VALUE(0)}
type="string"
value={editItem.value as string}
variant="outlined"
onChange={updateFormValue}
fullWidth
margin="normal"
/>
</Grid>
<Grid>
<TextField
name="uom"
label={LL.UNIT()}
value={editItem.uom}
margin="normal"
onChange={updateFormValue}
select
>
{DeviceValueUOM_s.map((val, i) => (
<MenuItem key={val} value={i}>
{val}
</MenuItem>
))}
</TextField>
</Grid>
</>
)}
{editItem.ram === 0 && (
<>
@@ -255,7 +289,7 @@ const CustomEntitiesDialog = ({
<TextField
name="factor"
label={LL.FACTOR()}
value={numberValue(editItem.factor)}
value={numberValue(editItem.factor as number)}
variant="outlined"
onChange={updateFormValue}
sx={{ width: '11ch' }}
@@ -291,16 +325,42 @@ const CustomEntitiesDialog = ({
<ValidatedTextField
fieldErrors={fieldErrors}
name="factor"
label="Bytes"
value={numberValue(editItem.factor)}
label={LL.BYTES()}
value={numberValue(editItem.factor as number)}
sx={{ width: '11ch' }}
variant="outlined"
onChange={updateFormValue}
margin="normal"
type="number"
slotProps={{
htmlInput: { step: '1', min: '1', max: '255' }
}}
/>
</Grid>
)}
{editItem.value_type === DeviceValueType.BOOL && (
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors}
name="factor"
label={LL.BITMASK()}
value={editItem.factor as string}
sx={{ width: '11ch' }}
variant="outlined"
onChange={updateFormValue}
margin="normal"
type="string"
slotProps={{
input: {
startAdornment: (
<InputAdornment position="start">0x</InputAdornment>
)
},
htmlInput: { style: { textTransform: 'uppercase' } }
}}
/>
</Grid>
)}
</>
)}
</Grid>
@@ -316,6 +376,15 @@ const CustomEntitiesDialog = ({
>
{LL.REMOVE()}
</Button>
<Button
sx={{ ml: 1 }}
startIcon={<AddIcon />}
variant="outlined"
color="primary"
onClick={dup}
>
{LL.DUPLICATE()}
</Button>
</Box>
)}
<Button

View File

@@ -16,6 +16,7 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid2 as Grid,
InputAdornment,
Link,
MenuItem,
@@ -24,7 +25,6 @@ import {
ToggleButtonGroup,
Typography
} from '@mui/material';
import Grid from '@mui/material/Grid2';
import {
Body,
@@ -38,7 +38,7 @@ import {
import { useTheme } from '@table-library/react-table-library/theme';
import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova/client';
import RestartMonitor from 'app/status/RestartMonitor';
import SystemMonitor from 'app/status/SystemMonitor';
import {
BlockNavigation,
ButtonRow,
@@ -593,7 +593,7 @@ const Customizations = () => {
</Button>
</Grid>
<Grid>
<Typography variant="subtitle2" color="primary">
<Typography variant="subtitle2" color="grey">
{LL.SHOWING()}&nbsp;{shown_data.length}/{deviceEntities.length}
&nbsp;{LL.ENTITIES(deviceEntities.length)}
</Typography>
@@ -737,7 +737,7 @@ const Customizations = () => {
return (
<SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <RestartMonitor /> : renderContent()}
{restarting ? <SystemMonitor /> : renderContent()}
{selectedDeviceEntity && (
<SettingsCustomizationsDialog
open={dialogOpen}

View File

@@ -10,10 +10,10 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid2 as Grid,
TextField,
Typography
} from '@mui/material';
import Grid from '@mui/material/Grid2';
import { dialogStyle } from 'CustomTheme';
import { useI18nContext } from 'i18n/i18n-react';

View File

@@ -1,10 +1,12 @@
import { useContext, useEffect, useState } from 'react';
import { IconContext } from 'react-icons/lib';
import { Link } from 'react-router';
import { toast } from 'react-toastify';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import EditIcon from '@mui/icons-material/Edit';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
import {
@@ -12,16 +14,20 @@ import {
IconButton,
ToggleButton,
ToggleButtonGroup,
Tooltip,
Typography
} from '@mui/material';
import Grid from '@mui/material/Grid2';
import { Body, Cell, Row, Table } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme';
import { CellTree, useTree } from '@table-library/react-table-library/tree';
import { useRequest } from 'alova/client';
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import {
ButtonTooltip,
FormLoader,
MessageBox,
SectionContent,
useLayoutTitle
} from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import { useInterval, usePersistState } from 'utils';
@@ -54,13 +60,12 @@ const Dashboard = () => {
const {
data,
send: fetchDashboard,
error,
loading
error
} = useRequest(readDashboard, {
initialData: []
initialData: { connected: true, nodes: [] }
}).onSuccess((event) => {
if (event.data.length !== parentNodes) {
setParentNodes(event.data.length); // count number of parents/devices
if (event.data.nodes.length !== parentNodes) {
setParentNodes(event.data.nodes.length); // count number of parents/devices
}
});
@@ -120,7 +125,7 @@ const Dashboard = () => {
});
const tree = useTree(
{ nodes: data },
{ nodes: data.nodes },
{
onChange: undefined // not used but needed
},
@@ -149,11 +154,11 @@ const Dashboard = () => {
if (!deviceValueDialogOpen) {
void fetchDashboard();
}
}, 3000);
});
useEffect(() => {
showAll
? tree.fns.onAddAll(data.map((item: DashboardItem) => item.id)) // expand tree
? tree.fns.onAddAll(data.nodes.map((item: DashboardItem) => item.id)) // expand tree
: tree.fns.onRemoveAll(); // collapse tree
}, [parentNodes]);
@@ -223,120 +228,133 @@ const Dashboard = () => {
return <FormLoader onRetry={fetchDashboard} errorMessage={error?.message} />;
}
const hasFavEntities = data.nodes.filter(
(item: DashboardItem) => item.id <= 90
).length;
return (
<>
<Box
sx={{
backgroundColor: 'black',
pt: 1,
pl: 2
}}
>
<Grid container spacing={0} justifyContent="flex-start">
<Grid size={11}>
<Typography mb={2} variant="body1" color="warning">
{LL.DASHBOARD_1()}.
</Typography>
</Grid>
{!data.connected && (
<MessageBox mb={2} level="error" message={LL.EMS_BUS_WARNING()} />
)}
<Grid size={1} alignItems="end">
<ToggleButtonGroup
color="primary"
size="small"
value={showAll}
exclusive
onChange={handleShowAll}
>
{data.connected && data.nodes.length > 0 && !hasFavEntities && (
<MessageBox mb={2} level="warning">
<Typography>
{LL.NO_DATA_1()}&nbsp;
<Link to="/customizations" style={{ color: 'white' }}>
{LL.CUSTOMIZATIONS()}
</Link>
&nbsp;{LL.NO_DATA_2()}&nbsp;
{LL.NO_DATA_3()}&nbsp;
<Link to="/devices" style={{ color: 'white' }}>
{LL.DEVICES()}
</Link>
.
</Typography>
</MessageBox>
)}
{data.nodes.length > 0 && (
<>
<ToggleButtonGroup
color="primary"
size="small"
value={showAll}
exclusive
onChange={handleShowAll}
>
<ButtonTooltip title={LL.ALLVALUES()} arrow>
<ToggleButton value={true}>
<UnfoldMoreIcon sx={{ fontSize: 18 }} />
</ToggleButton>
</ButtonTooltip>
<ButtonTooltip title={LL.COMPACT()} arrow>
<ToggleButton value={false}>
<UnfoldLessIcon sx={{ fontSize: 18 }} />
</ToggleButton>
</ToggleButtonGroup>
</Grid>
</Grid>
</Box>
</ButtonTooltip>
</ToggleButtonGroup>
<ButtonTooltip title={LL.DASHBOARD_1()} arrow>
<HelpOutlineIcon color="primary" sx={{ ml: 1, fontSize: 20 }} />
</ButtonTooltip>
<Box
padding={1}
justifyContent="center"
flexDirection="column"
sx={{
borderRadius: 1,
border: '1px solid grey'
}}
>
<IconContext.Provider
value={{
color: 'lightblue',
size: '16',
style: { verticalAlign: 'middle' }
}}
>
{!loading && data.length === 0 ? (
<Typography variant="subtitle2" color="secondary">
{LL.NO_DATA()}
</Typography>
) : (
<Table
data={{ nodes: data }}
theme={dashboard_theme}
layout={{ custom: true }}
tree={tree}
<Box
padding={1}
justifyContent="center"
flexDirection="column"
sx={{
borderRadius: 1,
border: '1px solid grey'
}}
>
<IconContext.Provider
value={{
color: 'lightblue',
size: '18',
style: { verticalAlign: 'middle' }
}}
>
{(tableList: DashboardItem[]) => (
<Body>
{tableList.map((di: DashboardItem) => (
<Row
key={di.id}
item={di}
onClick={() => editDashboardValue(di)}
>
{di.id > 99 ? (
<>
<Cell>{showName(di)}</Cell>
<Cell>
<Tooltip
placement="left"
title={formatValue(LL, di.dv?.v, di.dv?.u)}
arrow
>
<span>{formatValue(LL, di.dv?.v, di.dv?.u)}</span>
</Tooltip>
</Cell>
<Table
data={{ nodes: data.nodes }}
theme={dashboard_theme}
layout={{ custom: true }}
tree={tree}
>
{(tableList: DashboardItem[]) => (
<Body>
{tableList.map((di: DashboardItem) => (
<Row
key={di.id}
item={di}
onClick={() => editDashboardValue(di)}
>
{di.id > 99 ? (
<>
<Cell>{showName(di)}</Cell>
<Cell>
<ButtonTooltip
title={formatValue(LL, di.dv?.v, di.dv?.u)}
>
<span>{formatValue(LL, di.dv?.v, di.dv?.u)}</span>
</ButtonTooltip>
</Cell>
<Cell>
{me.admin &&
di.dv?.c &&
!hasMask(di.dv.id, DeviceEntityMask.DV_READONLY) && (
<IconButton
size="small"
onClick={() => editDashboardValue(di)}
>
<EditIcon
color="primary"
sx={{ fontSize: 16 }}
/>
</IconButton>
)}
</Cell>
</>
) : (
<>
<CellTree item={di}>{showName(di)}</CellTree>
<Cell />
<Cell />
</>
)}
</Row>
))}
</Body>
)}
</Table>
)}
</IconContext.Provider>
</Box>
<Cell>
{me.admin &&
di.dv?.c &&
!hasMask(
di.dv.id,
DeviceEntityMask.DV_READONLY
) && (
<IconButton
size="small"
onClick={() => editDashboardValue(di)}
>
<EditIcon
color="primary"
sx={{ fontSize: 16 }}
/>
</IconButton>
)}
</Cell>
</>
) : (
<>
<CellTree item={di}>{showName(di)}</CellTree>
<Cell />
<Cell />
</>
)}
</Row>
))}
</Body>
)}
</Table>
</IconContext.Provider>
</Box>
</>
)}
</>
);
};

View File

@@ -10,15 +10,16 @@ import { useNavigate } from 'react-router';
import { toast } from 'react-toastify';
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
import ConstructionIcon from '@mui/icons-material/Construction';
import EditIcon from '@mui/icons-material/Edit';
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
import DownloadIcon from '@mui/icons-material/GetApp';
import HighlightOffIcon from '@mui/icons-material/HighlightOff';
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 SearchIcon from '@mui/icons-material/Search';
import StarIcon from '@mui/icons-material/Star';
import StarBorderOutlinedIcon from '@mui/icons-material/StarBorderOutlined';
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
@@ -30,17 +31,16 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid2 as Grid,
IconButton,
InputAdornment,
List,
ListItem,
ListItemText,
Tooltip,
type TooltipProps,
Typography,
styled,
tooltipClasses
TextField,
ToggleButton,
Typography
} from '@mui/material';
import Grid from '@mui/material/Grid2';
import { useRowSelect } from '@table-library/react-table-library/select';
import { SortToggleType, useSort } from '@table-library/react-table-library/sort';
@@ -57,7 +57,12 @@ 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/client';
import { MessageBox, SectionContent, useLayoutTitle } from 'components';
import {
ButtonTooltip,
MessageBox,
SectionContent,
useLayoutTitle
} from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import { useInterval } from 'utils';
@@ -80,6 +85,7 @@ const Devices = () => {
const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false);
const [showDeviceInfo, setShowDeviceInfo] = useState(false);
const [selectedDevice, setSelectedDevice] = useState<number>();
const [search, setSearch] = useState('');
const navigate = useNavigate();
@@ -221,20 +227,6 @@ const Devices = () => {
}
]);
const ButtonTooltip = styled(({ className, ...props }: TooltipProps) => (
<Tooltip {...props} arrow classes={{ popper: className }} />
))(({ theme }) => ({
[`& .${tooltipClasses.arrow}`]: {
color: theme.palette.success.main
},
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: theme.palette.success.main,
color: 'rgba(0, 0, 0, 0.87)',
boxShadow: theme.shadows[1],
fontSize: 10
}
}));
const getSortIcon = (state: State, sortKey: unknown) => {
if (state.sortKey === sortKey && state.reverse) {
return <KeyboardArrowDownOutlinedIcon />;
@@ -284,6 +276,7 @@ const Devices = () => {
const resetDeviceSelect = () => {
device_select.fns.onRemoveAll();
setSearch('');
};
const escFunction = useCallback(
@@ -419,7 +412,7 @@ const Devices = () => {
if (!deviceValueDialogOpen) {
selectedDevice ? void sendDeviceData(selectedDevice) : void sendCoreData();
}
}, 3000);
});
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
const id = Number(device_select.state.id);
@@ -522,7 +515,7 @@ const Devices = () => {
<IconContext.Provider
value={{
color: 'lightblue',
size: '16',
size: '18',
style: { verticalAlign: 'middle' }
}}
>
@@ -604,8 +597,12 @@ const Devices = () => {
);
const shown_data = onlyFav
? deviceData.nodes.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE))
: deviceData.nodes;
? deviceData.nodes.filter(
(dv) =>
hasMask(dv.id, DeviceEntityMask.DV_FAVORITE) &&
dv.id.slice(2).includes(search)
)
: deviceData.nodes.filter((dv) => dv.id.slice(2).includes(search));
const deviceIndex = coreData.devices.findIndex(
(d) => d.id === device_select.state.id
@@ -628,56 +625,84 @@ const Devices = () => {
border: '1px solid #177ac9'
}}
>
<Box sx={{ border: '1px solid #177ac9' }}>
<Typography noWrap variant="subtitle1" color="warning.main" sx={{ ml: 1 }}>
{coreData.devices[deviceIndex].n}&nbsp;(
{coreData.devices[deviceIndex].tn})
</Typography>
<Box sx={{ p: 1 }}>
<Grid container justifyContent="space-between">
<Typography sx={{ ml: 1 }} variant="subtitle2" color="grey">
{LL.SHOWING() +
' ' +
shown_data.length +
'/' +
coreData.devices[deviceIndex].e +
' ' +
LL.ENTITIES(shown_data.length)}
<ButtonTooltip title="Info">
<IconButton onClick={() => setShowDeviceInfo(true)}>
<InfoOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
</IconButton>
</ButtonTooltip>
{me.admin && (
<ButtonTooltip title={LL.CUSTOMIZATIONS()}>
<IconButton onClick={customize}>
<FormatListNumberedIcon color="primary" sx={{ fontSize: 18 }} />
</IconButton>
</ButtonTooltip>
)}
<ButtonTooltip title={LL.EXPORT()}>
<IconButton onClick={handleDownloadCsv}>
<DownloadIcon color="primary" sx={{ fontSize: 18 }} />
</IconButton>
</ButtonTooltip>
<ButtonTooltip title={LL.FAVORITES()}>
<IconButton onClick={() => setOnlyFav(!onlyFav)}>
{onlyFav ? (
<StarIcon color="primary" sx={{ fontSize: 18 }} />
) : (
<StarBorderOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
)}
</IconButton>
</ButtonTooltip>
<Typography noWrap variant="subtitle1" color="warning.main">
{coreData.devices[deviceIndex].n}&nbsp;(
{coreData.devices[deviceIndex].tn})
</Typography>
<Grid justifyContent="flex-end">
<ButtonTooltip title={LL.CANCEL()}>
<ButtonTooltip title={LL.CLOSE()}>
<IconButton onClick={resetDeviceSelect}>
<HighlightOffIcon color="primary" sx={{ fontSize: 18 }} />
</IconButton>
</ButtonTooltip>
</Grid>
</Grid>
<TextField
size="small"
variant="outlined"
sx={{ width: '22ch' }}
placeholder={LL.SEARCH()}
onChange={(event) => {
setSearch(event.target.value);
}}
slotProps={{
input: {
startAdornment: (
<InputAdornment position="start">
<SearchIcon color="primary" sx={{ fontSize: 16 }} />
</InputAdornment>
)
}
}}
/>
<ButtonTooltip title={LL.DEVICE_DETAILS()}>
<IconButton onClick={() => setShowDeviceInfo(true)}>
<InfoOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
</IconButton>
</ButtonTooltip>
{me.admin && (
<ButtonTooltip title={LL.CUSTOMIZATIONS()}>
<IconButton onClick={customize}>
<ConstructionIcon color="primary" sx={{ fontSize: 18 }} />
</IconButton>
</ButtonTooltip>
)}
<ButtonTooltip title={LL.EXPORT()}>
<IconButton onClick={handleDownloadCsv}>
<DownloadIcon color="primary" sx={{ fontSize: 18 }} />
</IconButton>
</ButtonTooltip>
<ButtonTooltip title={LL.FAVORITES()}>
<ToggleButton
value="1"
size="small"
selected={onlyFav}
onChange={() => {
setOnlyFav(!onlyFav);
}}
>
{onlyFav ? (
<StarIcon color="primary" sx={{ fontSize: 18 }} />
) : (
<StarBorderOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
)}{' '}
</ToggleButton>
</ButtonTooltip>
<span style={{ color: 'grey', fontSize: '12px' }}>
&nbsp;
{LL.SHOWING() +
' ' +
shown_data.length +
'/' +
coreData.devices[deviceIndex].e +
' ' +
LL.ENTITIES(shown_data.length)}
</span>
</Box>
<Table

View File

@@ -11,12 +11,12 @@ import {
DialogContent,
DialogTitle,
FormHelperText,
Grid2 as Grid,
InputAdornment,
MenuItem,
TextField,
Typography
} from '@mui/material';
import Grid from '@mui/material/Grid2';
import { dialogStyle } from 'CustomTheme';
import type Schema from 'async-validator';

View File

@@ -51,20 +51,6 @@ const Help = () => {
}
});
// const { send: sendExportAllValues } = useRequest(
// () => callAction({ action: 'export', param: 'allvalues' }),
// {
// immediate: false
// }
// )
// .onSuccess((event) => {
// saveFile(event.data, 'allvalues', '.txt');
// toast.info(LL.DOWNLOAD_SUCCESSFUL());
// })
// .onError((error) => {
// toast.error(error.message);
// });
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
immediate: false
})
@@ -114,7 +100,12 @@ const Help = () => {
{me.admin && (
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
<ListItem>
<ListItemButton component="a" href="https://docs.emsesp.org">
<ListItemButton
component="a"
target="_blank"
rel="noreferrer"
href="https://docs.emsesp.org"
>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#72caf9' }}>
<MenuBookIcon />
@@ -125,7 +116,12 @@ const Help = () => {
</ListItem>
<ListItem>
<ListItemButton component="a" href="https://discord.gg/3J3GgnzpyT">
<ListItemButton
component="a"
target="_blank"
rel="noreferrer"
href="https://discord.gg/3J3GgnzpyT"
>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#72caf9' }}>
<CommentIcon />
@@ -138,6 +134,8 @@ const Help = () => {
<ListItem>
<ListItemButton
component="a"
target="_blank"
rel="noreferrer"
href="https://github.com/emsesp/EMS-ESP32/issues/new/choose"
>
<ListItemAvatar>
@@ -165,21 +163,16 @@ const Help = () => {
</Button>
</Box>
{/* <Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => sendExportAllValues()}
>
{LL.DOWNLOAD(1)}&nbsp;{LL.ALLVALUES()}
</Button> */}
<Divider sx={{ mt: 4 }} />
<Typography color="white" variant="subtitle1" align="center" mt={1}>
&copy;&nbsp;
<Link target="_blank" href="https://emsesp.org" color="primary">
<Link
target="_blank"
rel="noreferrer"
href="https://emsesp.org"
color="primary"
>
{'emsesp.org'}
</Link>
</Typography>

View File

@@ -10,9 +10,9 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid2 as Grid,
TextField
} from '@mui/material';
import Grid from '@mui/material/Grid2';
import { dialogStyle } from 'CustomTheme';
import { BlockFormControlLabel } from 'components';

View File

@@ -13,12 +13,12 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid2 as Grid,
TextField,
ToggleButton,
ToggleButtonGroup,
Typography
} from '@mui/material';
import Grid from '@mui/material/Grid2';
import { dialogStyle } from 'CustomTheme';
import type Schema from 'async-validator';

View File

@@ -90,7 +90,7 @@ const Sensors = () => {
if (!temperatureDialogOpen && !analogDialogOpen) {
void fetchSensorData();
}
}, 3000);
});
const common_theme = useTheme({
BaseRow: `

View File

@@ -10,12 +10,12 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid2 as Grid,
InputAdornment,
MenuItem,
TextField,
Typography
} from '@mui/material';
import Grid from '@mui/material/Grid2';
import { dialogStyle } from 'CustomTheme';
import type Schema from 'async-validator';

View File

@@ -9,11 +9,11 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid2 as Grid,
InputAdornment,
TextField,
Typography
} from '@mui/material';
import Grid from '@mui/material/Grid2';
import { dialogStyle } from 'CustomTheme';
import type Schema from 'async-validator';

View File

@@ -34,7 +34,12 @@ export function formatValue(
if (value === undefined || typeof value === 'boolean') {
return '';
}
return value as string;
return (
(value as string) +
(value === '' || uom === undefined || uom === 0
? ''
: ' ' + DeviceValueUOM_s[uom])
);
}
switch (uom) {

View File

@@ -21,6 +21,7 @@ export interface Settings {
dallas_gpio: number;
dallas_parasite: boolean;
led_gpio: number;
led_type: number;
hide_led: boolean;
low_clock: boolean;
notoken_api: boolean;
@@ -71,7 +72,7 @@ export interface Device {
d: number; // deviceid
p: number; // productid
v: string; // version
e: number; // entities
e: number; // total number of entities
url?: string; // lowercase type name used in API URL
}
@@ -123,6 +124,11 @@ export interface DashboardItem {
nodes?: DashboardItem[]; // children nodes, optional
}
export interface DashboardData {
connected: boolean; // true if connected to EMS bus
nodes: DashboardItem[];
}
export interface DeviceValue {
id: string; // index, contains mask+name
v?: unknown; // value, Number, String or Boolean - can be undefined
@@ -262,6 +268,7 @@ export const BOARD_PROFILES: BoardProfiles = {
export interface BoardProfile {
board_profile: string;
led_gpio: number;
led_type: number;
dallas_gpio: number;
rx_gpio: number;
tx_gpio: number;
@@ -368,7 +375,7 @@ export interface EntityItem {
device_id: number | string;
type_id: number | string;
offset: number;
factor: number;
factor: number | string;
uom: number;
value_type: number;
value?: unknown;
@@ -380,7 +387,7 @@ export interface EntityItem {
o_device_id?: number | string;
o_type_id?: number | string;
o_offset?: number;
o_factor?: number;
o_factor?: number | string;
o_uom?: number;
o_value_type?: number;
o_deleted?: boolean;

View File

@@ -382,10 +382,7 @@ export const entityItemValidation = (entity: EntityItem[], entityItem: EntityIte
{ required: true, message: 'Offset is required' },
{ type: 'number', min: 0, max: 255, message: 'Must be between 0 and 255' }
],
factor: [
{ required: true, message: 'is required' },
{ type: 'number', message: 'Must be a number' }
]
factor: [{ required: true, message: 'is required' }]
});
export const uniqueTemperatureNameValidator = (

View File

@@ -9,17 +9,17 @@ import {
Button,
Checkbox,
Divider,
Grid2 as Grid,
InputAdornment,
MenuItem,
TextField,
Typography
} from '@mui/material';
import Grid from '@mui/material/Grid2';
import { readSystemStatus } from 'api/system';
import { useRequest } from 'alova/client';
import RestartMonitor from 'app/status/RestartMonitor';
import SystemMonitor from 'app/status/SystemMonitor';
import type { ValidateFieldsError } from 'async-validator';
import {
BlockFormControlLabel,
@@ -126,9 +126,6 @@ const ApplicationSettings = () => {
const SecondsInputProps = {
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
};
const MilliSecondsInputProps = {
endAdornment: <InputAdornment position="end">ms</InputAdornment>
};
const MinutesInputProps = {
endAdornment: <InputAdornment position="end">{LL.MINUTES()}</InputAdornment>
};
@@ -207,7 +204,16 @@ const ApplicationSettings = () => {
disabled={!hardwareData.psram}
/>
}
label={LL.ENABLE_MODBUS()}
label={
<Typography color={!hardwareData.psram ? 'grey' : 'default'}>
{LL.ENABLE_MODBUS()}
{!hardwareData.psram && (
<Typography variant="caption">
&nbsp; &#40;{LL.IS_REQUIRED('PSRAM')}&#41;
</Typography>
)}
</Typography>
}
/>
{data.modbus_enabled && (
<Grid container spacing={2} rowSpacing={0}>
@@ -241,7 +247,7 @@ const ApplicationSettings = () => {
name="modbus_timeout"
label="Timeout"
slotProps={{
input: MilliSecondsInputProps
input: SecondsInputProps
}}
variant="outlined"
value={numberValue(data.modbus_timeout)}
@@ -544,6 +550,23 @@ const ApplicationSettings = () => {
margin="normal"
/>
</Grid>
{data.led_gpio !== 0 && (
<Grid>
<TextField
name="led_type"
label={'LED ' + LL.TYPE()}
value={data.led_type}
fullWidth
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={0}>LED</MenuItem>
<MenuItem value={1}>RGB-LED</MenuItem>
</TextField>
</Grid>
)}
<Grid>
<TextField
name="phy_type"
@@ -853,7 +876,7 @@ const ApplicationSettings = () => {
return (
<SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <RestartMonitor /> : content()}
{restarting ? <SystemMonitor /> : content()}
</SectionContent>
);
};

View File

@@ -2,15 +2,14 @@ import { useState } from 'react';
import { toast } from 'react-toastify';
import DownloadIcon from '@mui/icons-material/GetApp';
import { Box, Button, Typography } from '@mui/material';
import Grid from '@mui/material/Grid2';
import { Box, Button, Grid2 as Grid, Typography } from '@mui/material';
import * as SystemApi from 'api/system';
import { API, callAction } from 'api/app';
import { useRequest } from 'alova/client';
import type { APIcall } from 'app/main/types';
import RestartMonitor from 'app/status/RestartMonitor';
import SystemMonitor from 'app/status/SystemMonitor';
import {
FormLoader,
SectionContent,
@@ -109,6 +108,15 @@ const DownloadUpload = () => {
{LL.SCHEDULE(0)}
</Button>
</Grid>
<Button
sx={{ ml: 2, mt: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => sendExportData('allvalues')}
>
{LL.ALLVALUES()}
</Button>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.UPLOAD()}
@@ -118,13 +126,13 @@ const DownloadUpload = () => {
<Typography variant="body1">{LL.UPLOAD_TEXT()}.</Typography>
</Box>
<SingleUpload doRestart={doRestart} />
<SingleUpload text={LL.UPLOAD_DRAG()} doRestart={doRestart} />
</>
);
};
return (
<SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>
<SectionContent>{restarting ? <SystemMonitor /> : content()}</SectionContent>
);
};

View File

@@ -5,12 +5,12 @@ import WarningIcon from '@mui/icons-material/Warning';
import {
Button,
Checkbox,
Grid2 as Grid,
InputAdornment,
MenuItem,
TextField,
Typography
} from '@mui/material';
import Grid from '@mui/material/Grid2';
import * as MqttApi from 'api/mqtt';

View File

@@ -1,7 +1,6 @@
import { useState } from 'react';
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import BuildIcon from '@mui/icons-material/Build';
import CancelIcon from '@mui/icons-material/Cancel';
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import ImportExportIcon from '@mui/icons-material/ImportExport';
@@ -21,7 +20,7 @@ import {
List
} from '@mui/material';
import { API, callAction } from 'api/app';
import { API } from 'api/app';
import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova/client';
@@ -40,11 +39,6 @@ const Settings = () => {
immediate: false
});
// call checkUpgrade with no param to fetch EMS-ESP version
const { data } = useRequest(() => callAction({ action: 'checkUpgrade' }), {
initialData: { emsesp_version: '...' }
});
const doFormat = async () => {
await sendAPI({ device: 'system', cmd: 'format', id: 0 }).then(() => {
setConfirmFactoryReset(false);
@@ -83,14 +77,6 @@ const Settings = () => {
const content = () => (
<>
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
<ListMenuItem
icon={BuildIcon}
bgcolor="#72caf9"
label="EMS-ESP Firmware"
text={'v' + data.emsesp_version}
to="version"
/>
<ListMenuItem
icon={TuneIcon}
bgcolor="#134ba2"
@@ -151,7 +137,7 @@ const Settings = () => {
bgcolor="#5d89f7"
label={LL.DOWNLOAD_UPLOAD()}
text={LL.DOWNLOAD_UPLOAD_1()}
to="upload"
to="downloadUpload"
/>
</List>

View File

@@ -1,9 +1,16 @@
import { useCallback, useState } from 'react';
import { Navigate, Route, Routes, useNavigate } from 'react-router';
import {
Navigate,
Route,
Routes,
matchRoutes,
useLocation,
useNavigate
} from 'react-router';
import { Tab } from '@mui/material';
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { RouterTabs, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { WiFiNetwork } from 'types';
@@ -15,7 +22,20 @@ const Network = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.NETWORK(0));
const { routerTab } = useRouterTab();
// this also works!
// const routerTab = useMatch(`settings/network/:path/*`)?.pathname || false;
const matchedRoutes = matchRoutes(
[
{
path: '/settings/network/settings',
element: <NetworkSettings />,
dog: 'woof'
},
{ path: '/settings/network/scan', element: <WiFiNetworkScanner /> }
],
useLocation()
);
const routerTab = matchedRoutes?.[0].route.path || false;
const navigate = useNavigate();
@@ -24,7 +44,7 @@ const Network = () => {
const selectNetwork = useCallback(
(network: WiFiNetwork) => {
setSelectedNetwork(network);
void navigate('/settings');
void navigate('/settings/network/settings');
},
[navigate]
);
@@ -42,13 +62,19 @@ const Network = () => {
}}
>
<RouterTabs value={routerTab}>
<Tab value="/settings/network/settings" label={LL.SETTINGS_OF(LL.NETWORK(1))} />
<Tab
value="/settings/network/settings"
label={LL.SETTINGS_OF(LL.NETWORK(1))}
/>
<Tab value="/settings/network/scan" label={LL.NETWORK_SCAN()} />
</RouterTabs>
<Routes>
<Route path="scan" element={<WiFiNetworkScanner />} />
<Route path="settings" element={<NetworkSettings />} />
<Route path="*" element={<Navigate replace to="settings" />} />
<Route
path="*"
element={<Navigate replace to="/settings/network/settings" />}
/>
</Routes>
</WiFiConnectionContext.Provider>
);

View File

@@ -43,7 +43,7 @@ import { updateValueDirty, useRest } from 'utils';
import { validate } from 'validators';
import { createNetworkSettingsValidator } from 'validators/network';
import RestartMonitor from '../../status/RestartMonitor';
import SystemMonitor from '../../status/SystemMonitor';
import { WiFiConnectionContext } from './WiFiConnectionContext';
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
@@ -400,7 +400,7 @@ const NetworkSettings = () => {
return (
<SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <RestartMonitor /> : content()}
{restarting ? <SystemMonitor /> : content()}
</SectionContent>
);
};

View File

@@ -1,8 +1,8 @@
import { Navigate, Route, Routes } from 'react-router';
import { Navigate, Route, Routes, matchRoutes, useLocation } from 'react-router';
import { Tab } from '@mui/material';
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { RouterTabs, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import ManageUsers from './ManageUsers';
@@ -12,18 +12,31 @@ const Security = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.SECURITY(0));
const { routerTab } = useRouterTab();
const matchedRoutes = matchRoutes(
[
{ path: '/settings/security/settings', element: <ManageUsers />, dog: 'woof' },
{ path: '/settings/security/users', element: <SecuritySettings /> }
],
useLocation()
);
const routerTab = matchedRoutes?.[0].route.path || false;
return (
<>
<RouterTabs value={routerTab}>
<Tab value="/settings/security/settings" label={LL.SETTINGS_OF(LL.SECURITY(1))} />
<Tab
value="/settings/security/settings"
label={LL.SETTINGS_OF(LL.SECURITY(1))}
/>
<Tab value="/settings/security/users" label={LL.MANAGE_USERS()} />
</RouterTabs>
<Routes>
<Route path="users" element={<ManageUsers />} />
<Route path="settings" element={<SecuritySettings />} />
<Route path="*" element={<Navigate replace to="settings" />} />
<Route
path="*"
element={<Navigate replace to="/settings/security/settings" />}
/>
</Routes>
</>
);

View File

@@ -14,11 +14,12 @@ import type { Theme } from '@mui/material';
import * as APApi from 'api/ap';
import { useAutoRequest } from 'alova/client';
import { useRequest } from 'alova/client';
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { APStatusType } from 'types';
import { APNetworkStatus } from 'types';
import { useInterval } from 'utils';
export const apStatusHighlight = ({ status }: APStatusType, theme: Theme) => {
switch (status) {
@@ -34,11 +35,11 @@ export const apStatusHighlight = ({ status }: APStatusType, theme: Theme) => {
};
const APStatus = () => {
const {
data,
send: loadData,
error
} = useAutoRequest(APApi.readAPStatus, { pollingTime: 3000 });
const { data, send: loadData, error } = useRequest(APApi.readAPStatus);
useInterval(() => {
void loadData();
});
const { LL } = useI18nContext();
useLayoutTitle(LL.ACCESS_POINT(0));

View File

@@ -8,20 +8,21 @@ import {
Table
} from '@table-library/react-table-library/table';
import { useTheme as tableTheme } from '@table-library/react-table-library/theme';
import { useAutoRequest } from 'alova/client';
import { useRequest } from 'alova/client';
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { Translation } from 'i18n/i18n-types';
import { useInterval } from 'utils';
import { readActivity } from '../../api/app';
import type { Stat } from '../main/types';
const SystemActivity = () => {
const {
data,
send: loadData,
error
} = useAutoRequest(readActivity, { pollingTime: 3000 });
const { data, send: loadData, error } = useRequest(readActivity);
useInterval(() => {
void loadData();
});
const { LL } = useI18nContext();

View File

@@ -17,9 +17,10 @@ import {
import * as SystemApi from 'api/system';
import { useAutoRequest } from 'alova/client';
import { useRequest } from 'alova/client';
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { useInterval } from 'utils';
import BBQKeesIcon from './bbqkees.svg';
@@ -32,11 +33,11 @@ const HardwareStatus = () => {
useLayoutTitle(LL.HARDWARE());
const {
data,
send: loadData,
error
} = useAutoRequest(SystemApi.readSystemStatus, { pollingTime: 3000 });
const { data, send: loadData, error } = useRequest(SystemApi.readSystemStatus);
useInterval(() => {
void loadData();
});
const content = () => {
if (!data) {
@@ -98,7 +99,13 @@ const HardwareStatus = () => {
' @ ' +
data.cpu_freq_mhz +
' Mhz' +
(data.temperature ? ', T: ' + data.temperature + ' °C' : '')
// bit of a hack : if the CPU temp is higher than 90 (=32 Fahrenheit if using Celsius), show F, otherwise C
(data.temperature
? ', T: ' +
data.temperature +
' °' +
(data.temperature > 90 ? 'F' : 'C')
: '')
}
/>
</ListItem>

View File

@@ -15,11 +15,12 @@ import type { Theme } from '@mui/material';
import * as MqttApi from 'api/mqtt';
import { useAutoRequest } from 'alova/client';
import { useRequest } from 'alova/client';
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { MqttStatusType } from 'types';
import { MqttDisconnectReason } from 'types';
import { useInterval } from 'utils';
export const mqttStatusHighlight = (
{ enabled, connected }: MqttStatusType,
@@ -54,11 +55,11 @@ export const mqttQueueHighlight = (
};
const MqttStatus = () => {
const {
data,
send: loadData,
error
} = useAutoRequest(MqttApi.readMqttStatus, { pollingTime: 3000 });
const { data, send: loadData, error } = useRequest(MqttApi.readMqttStatus);
useInterval(() => {
void loadData();
});
const { LL } = useI18nContext();
useLayoutTitle('MQTT');

View File

@@ -28,19 +28,20 @@ import type { Theme } from '@mui/material';
import * as NTPApi from 'api/ntp';
import { dialogStyle } from 'CustomTheme';
import { useAutoRequest, useRequest } from 'alova/client';
import { 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 { useInterval } from 'utils';
import { formatDateTime, formatLocalDateTime } from 'utils';
const NTPStatus = () => {
const {
data,
send: loadData,
error
} = useAutoRequest(NTPApi.readNTPStatus, { pollingTime: 3000 });
const { data, send: loadData, error } = useRequest(NTPApi.readNTPStatus);
useInterval(() => {
void loadData();
});
const [localTime, setLocalTime] = useState<string>('');
const [settingTime, setSettingTime] = useState<boolean>(false);

View File

@@ -18,11 +18,12 @@ import type { Theme } from '@mui/material';
import * as NetworkApi from 'api/network';
import { useAutoRequest } from 'alova/client';
import { useRequest } from 'alova/client';
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { NetworkStatusType } from 'types';
import { NetworkConnectionStatus } from 'types';
import { useInterval } from 'utils';
const isConnected = ({ status }: NetworkStatusType) =>
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED ||
@@ -81,11 +82,11 @@ const IPs = (status: NetworkStatusType) => {
};
const NetworkStatus = () => {
const {
data,
send: loadData,
error
} = useAutoRequest(NetworkApi.readNetworkStatus, { pollingTime: 3000 });
const { data, send: loadData, error } = useRequest(NetworkApi.readNetworkStatus);
useInterval(() => {
void loadData();
});
const { LL } = useI18nContext();
useLayoutTitle(LL.NETWORK(1));

View File

@@ -1,81 +0,0 @@
import { useState } from 'react';
import {
Box,
CircularProgress,
Dialog,
DialogContent,
Typography
} from '@mui/material';
import { readSystemStatus } from 'api/system';
import { dialogStyle } from 'CustomTheme';
import { useAutoRequest } from 'alova/client';
import MessageBox from 'components/MessageBox';
import { useI18nContext } from 'i18n/i18n-react';
const RestartMonitor = () => {
const [errorMessage, setErrorMessage] = useState<string>();
const { LL } = useI18nContext();
let count = 0;
const { data } = useAutoRequest(readSystemStatus, {
pollingTime: 1000,
force: true,
initialData: { status: 'Getting ready...' },
async middleware(_, next) {
if (count++ >= 1) {
// skip first request (1 second) to allow AsyncWS to send its response
await next();
}
}
})
.onSuccess((event) => {
if (event.data.status === 'ready' || event.data.status === undefined) {
document.location.href = '/';
}
})
.onError((error) => {
setErrorMessage(error.message);
});
return (
<Dialog fullWidth={true} sx={dialogStyle} open={true}>
<DialogContent dividers>
<Box m={0} py={0} display="flex" alignItems="center" flexDirection="column">
<Typography
color="secondary"
variant="h6"
fontWeight={400}
textAlign="center"
>
{data?.status === 'uploading'
? LL.WAIT_FIRMWARE()
: data?.status === 'restarting'
? LL.APPLICATION_RESTARTING()
: data?.status === 'ready'
? LL.RESTARTING_PRE()
: LL.RESTARTING_POST()}
&hellip;
</Typography>
<Typography mt={2} variant="h6" fontWeight={400} textAlign="center">
{LL.PLEASE_WAIT()}
</Typography>
{errorMessage ? (
<MessageBox my={2} level="error" message={errorMessage} />
) : (
<Box py={2}>
<CircularProgress size={32} />
</Box>
)}
</Box>
</DialogContent>
</Dialog>
);
};
export default RestartMonitor;

View File

@@ -2,6 +2,7 @@ import { useContext, useState } from 'react';
import { toast } from 'react-toastify';
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import BuildIcon from '@mui/icons-material/Build';
import CancelIcon from '@mui/icons-material/Cancel';
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import DirectionsBusIcon from '@mui/icons-material/DirectionsBus';
@@ -30,15 +31,17 @@ import { API } from 'api/app';
import { readSystemStatus } from 'api/system';
import { dialogStyle } from 'CustomTheme';
import { useAutoRequest, useRequest } from 'alova/client';
import { useRequest } from 'alova/client';
import { type APIcall, busConnectionStatus } from 'app/main/types';
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import ListMenuItem from 'components/layout/ListMenuItem';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import { NTPSyncStatus, NetworkConnectionStatus } from 'types';
import { useInterval } from 'utils';
import { formatDateTime } from 'utils/time';
import RestartMonitor from './RestartMonitor';
import SystemMonitor from './SystemMonitor';
const SystemStatus = () => {
const { LL } = useI18nContext();
@@ -58,9 +61,8 @@ const SystemStatus = () => {
data,
send: loadData,
error
} = useAutoRequest(readSystemStatus, {
} = useRequest(readSystemStatus, {
initialData: [],
pollingTime: 3000,
async middleware(_, next) {
if (!restarting) {
await next();
@@ -68,6 +70,10 @@ const SystemStatus = () => {
}
});
useInterval(() => {
void loadData();
});
const theme = useTheme();
const formatDurationSec = (duration_sec: number) => {
@@ -134,7 +140,12 @@ const SystemStatus = () => {
case NTPSyncStatus.NTP_INACTIVE:
return LL.INACTIVE(0);
case NTPSyncStatus.NTP_ACTIVE:
return LL.ACTIVE();
return (
LL.ACTIVE() +
(data.ntp_time !== undefined
? ' (' + formatDateTime(data.ntp_time) + ')'
: '')
);
default:
return LL.UNKNOWN();
}
@@ -243,6 +254,14 @@ const SystemStatus = () => {
return (
<>
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
<ListMenuItem
icon={BuildIcon}
bgcolor="#72caf9"
label="EMS-ESP Firmware"
text={'v' + data.emsesp_version}
to="version"
/>
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#c5572c', color: 'white' }}>
@@ -301,7 +320,7 @@ const SystemStatus = () => {
icon={DeviceHubIcon}
bgcolor={activeHighlight(data.mqtt_status)}
label="MQTT"
text={data.mqtt_status ? LL.ACTIVE() : LL.INACTIVE(0)}
text={data.mqtt_status ? LL.CONNECTED(0) : LL.INACTIVE(0)}
to="/status/mqtt"
/>
@@ -339,7 +358,7 @@ const SystemStatus = () => {
};
return (
<SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>
<SectionContent>{restarting ? <SystemMonitor /> : content()}</SectionContent>
);
};

View File

@@ -8,12 +8,12 @@ import {
Box,
Button,
Checkbox,
Grid2 as Grid,
IconButton,
MenuItem,
TextField,
styled
} from '@mui/material';
import Grid from '@mui/material/Grid2';
import { API } from 'api/app';
import { fetchLogES, readLogSettings, updateLogSettings } from 'api/system';
@@ -101,6 +101,7 @@ const SystemLog = () => {
const [readOpen, setReadOpen] = useState(false);
const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
const [autoscroll, setAutoscroll] = useState(true);
const [lastId, setLastId] = useState<number>(-1);
const ALPHA_NUMERIC_DASH_REGEX = /^[a-fA-F0-9 ]+$/;
@@ -115,10 +116,13 @@ const SystemLog = () => {
immediate: true,
interceptByGlobalResponded: false
})
.onMessage((message: { id: number; data: string }) => {
.onMessage((message: { data: string }) => {
const rawData = message.data;
const logentry = JSON.parse(rawData) as LogEntry;
setLogEntries((log) => [...log, logentry]);
if (lastId < logentry.i) {
setLogEntries((log) => [...log, logentry]);
setLastId(logentry.i);
}
})
.onError(() => {
toast.error('No connection to Log service');
@@ -197,7 +201,7 @@ const SystemLog = () => {
name="level"
label={LL.LOG_LEVEL()}
value={data.level}
sx={{ width: '10ch' }}
sx={{ width: '14ch' }}
variant="outlined"
onChange={updateFormValue}
margin="normal"

View File

@@ -0,0 +1,125 @@
import { useState } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import { Box, Button, Dialog, DialogContent, Typography } from '@mui/material';
import { callAction } from 'api/app';
import { readSystemStatus } from 'api/system';
import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova/client';
import MessageBox from 'components/MessageBox';
import { useI18nContext } from 'i18n/i18n-react';
import { SystemStatusCodes } from 'types';
import { useInterval } from 'utils';
import { LinearProgressWithLabel } from '../../components/upload/LinearProgressWithLabel';
const SystemMonitor = () => {
const [errorMessage, setErrorMessage] = useState<string>();
const { LL } = useI18nContext();
let count = 0;
const { send: setSystemStatus } = useRequest(
(status: string) => callAction({ action: 'systemStatus', param: status }),
{
immediate: false
}
);
const { data, send } = useRequest(readSystemStatus, {
force: true,
async middleware(_, next) {
if (count++ >= 1) {
// skip first request (1 second) to allow AsyncWS to send its response
await next();
}
}
})
.onSuccess((event) => {
if (
event.data.status === SystemStatusCodes.SYSTEM_STATUS_NORMAL ||
event.data.status === undefined
) {
document.location.href = '/';
} else if (
event.data.status === SystemStatusCodes.SYSTEM_STATUS_ERROR_UPLOAD
) {
setErrorMessage('Please check system logs for possible causes');
}
})
.onError((error) => {
setErrorMessage(error.message);
});
useInterval(() => {
void send();
}, 1000); // check every 1 second
const onCancel = async () => {
setErrorMessage(undefined);
await setSystemStatus(
SystemStatusCodes.SYSTEM_STATUS_NORMAL as unknown as string
);
document.location.href = '/';
};
return (
<Dialog fullWidth={true} sx={dialogStyle} open={true}>
<DialogContent dividers>
<Box m={0} py={0} display="flex" alignItems="center" flexDirection="column">
<Typography
color="secondary"
variant="h6"
fontWeight={400}
textAlign="center"
>
{data?.status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING
? LL.WAIT_FIRMWARE()
: data?.status === SystemStatusCodes.SYSTEM_STATUS_PENDING_RESTART
? LL.APPLICATION_RESTARTING()
: data?.status === SystemStatusCodes.SYSTEM_STATUS_NORMAL
? LL.RESTARTING_PRE()
: data?.status === SystemStatusCodes.SYSTEM_STATUS_ERROR_UPLOAD
? 'Upload Failed'
: LL.RESTARTING_POST()}
</Typography>
{errorMessage ? (
<MessageBox my={2} level="error" message={errorMessage}>
<Button
size="small"
sx={{ ml: 2 }}
startIcon={<CancelIcon />}
variant="contained"
color="error"
onClick={onCancel}
>
{LL.RESET(0)}
</Button>
</MessageBox>
) : (
<>
<Typography mt={2} variant="h6" fontWeight={400} textAlign="center">
{LL.PLEASE_WAIT()}&hellip;
</Typography>
{data && data.status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING && (
<Box width="100%" pl={2} pr={2} py={2}>
<LinearProgressWithLabel
value={Math.round(
data?.status - SystemStatusCodes.SYSTEM_STATUS_UPLOADING
)}
/>
</Box>
)}
</>
)}
</Box>
</DialogContent>
</Dialog>
);
};
export default SystemMonitor;

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useContext, useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel';
@@ -9,27 +9,38 @@ import WarningIcon from '@mui/icons-material/Warning';
import {
Box,
Button,
Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
FormControlLabel,
Grid2 as Grid,
Link,
Typography
} from '@mui/material';
import Grid from '@mui/material/Grid2';
import * as SystemApi from 'api/system';
import { callAction } from 'api/app';
import { API, callAction } from 'api/app';
import { getDevVersion, getStableVersion } from 'api/system';
import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova/client';
import RestartMonitor from 'app/status/RestartMonitor';
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import type { APIcall } from 'app/main/types';
import SystemMonitor from 'app/status/SystemMonitor';
import {
FormLoader,
SectionContent,
SingleUpload,
useLayoutTitle
} from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
const Version = () => {
const { LL } = useI18nContext();
const { LL, locale } = useI18nContext();
const { me } = useContext(AuthenticatedContext);
const [restarting, setRestarting] = useState<boolean>(false);
const [openInstallDialog, setOpenInstallDialog] = useState<boolean>(false);
const [usingDevVersion, setUsingDevVersion] = useState<boolean>(false);
@@ -60,8 +71,10 @@ const Version = () => {
send: loadData,
error
} = useRequest(SystemApi.readSystemStatus).onSuccess((event) => {
// older version of EMS-ESP didn't have the psram set, so we can't do an OTA upgrade
setDownloadOnly(event.data.psram === undefined);
// older version of EMS-ESP on 4MB boards, can't use OTA because of SSL support in HttpClient
if (event.data.arduino_version.startsWith('Tasmota')) {
setDownloadOnly(true);
}
setUsingDevVersion(event.data.emsesp_version.includes('dev'));
});
@@ -78,7 +91,7 @@ const Version = () => {
useEffect(() => {
if (latestVersion && latestDevVersion) {
sendCheckUpgrade(latestDevVersion + ',' + latestVersion)
sendCheckUpgrade(latestDevVersion.name + ',' + latestVersion.name)
.catch((error: Error) => {
toast.error('Failed to check for upgrades: ' + error.message);
})
@@ -88,19 +101,59 @@ const Version = () => {
}
}, [latestVersion, latestDevVersion]);
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
const DIVISIONS = [
{ amount: 60, name: 'seconds' },
{ amount: 60, name: 'minutes' },
{ amount: 24, name: 'hours' },
{ amount: 7, name: 'days' },
{ amount: 4.34524, name: 'weeks' },
{ amount: 12, name: 'months' },
{ amount: Number.POSITIVE_INFINITY, name: 'years' }
];
function formatTimeAgo(date) {
let duration = (date.getTime() - new Date().getTime()) / 1000;
for (let i = 0; i < DIVISIONS.length; i++) {
const division = DIVISIONS[i];
if (Math.abs(duration) < division.amount) {
return rtf.format(
Math.round(duration),
division.name as Intl.RelativeTimeFormatUnit
);
}
duration /= division.amount;
}
}
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
immediate: false
});
const doRestart = async () => {
setRestarting(true);
await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch(
(error: Error) => {
toast.error(error.message);
}
);
};
const getBinURL = () => {
if (!latestVersion || !latestDevVersion) {
if (!internetLive) {
return '';
}
const filename =
'EMS-ESP-' +
(usingDevVersion ? latestDevVersion : latestVersion).replaceAll('.', '_') +
(usingDevVersion ? latestDevVersion.name : latestVersion.name).replaceAll(
'.',
'_'
) +
'-' +
getPlatform() +
'.bin';
return usingDevVersion
? DEV_URL + filename
: STABLE_URL + 'v' + latestVersion + '/' + filename;
: STABLE_URL + 'v' + latestVersion.name + '/' + filename;
};
const getPlatform = () => {
@@ -133,7 +186,9 @@ const Version = () => {
</DialogTitle>
<DialogContent dividers>
<Typography mb={2}>
{LL.INSTALL_VERSION(usingDevVersion ? latestDevVersion : latestVersion)}
{LL.INSTALL_VERSION(
usingDevVersion ? latestDevVersion?.name : latestVersion?.name
)}
</Typography>
</DialogContent>
<DialogActions>
@@ -177,25 +232,20 @@ const Version = () => {
setUsingDevVersion(data.emsesp_version.includes('dev'));
};
const switchToDev = () => {
setUsingDevVersion(true);
setUpgradeAvailable(true);
};
const showButtons = () => {
if (!upgradeAvailable) {
const showButtons = (showDev?: boolean) => {
if (!me.admin) {
return;
}
if (downloadOnly) {
return (
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
onClick={() => setOpenInstallDialog(false)}
color="warning"
size="small"
sx={{ ml: 2 }}
>
<Link underline="none" target="_blank" href={getBinURL()} color="warning">
{LL.DOWNLOAD(1)}
@@ -212,7 +262,10 @@ const Version = () => {
size="small"
onClick={() => showFirmwareDialog()}
>
{LL.UPGRADE()}&hellip;
{upgradeAvailable || (!usingDevVersion && showDev)
? LL.UPGRADE()
: LL.REINSTALL()}
&hellip;
</Button>
);
};
@@ -222,27 +275,29 @@ const Version = () => {
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
const isDev = data.emsesp_version.includes('dev');
return (
<>
<Box p={2} border="1px solid grey" borderRadius={2}>
<Typography sx={{ pb: 2 }} variant="h6" color="primary">
Firmware Version
<Typography mb={2} variant="h6" color="primary">
{LL.THIS_VERSION()}
</Typography>
<Grid container spacing={4}>
<Grid mb={1}>
<Typography mb={1} color="secondary">
{LL.VERSION()}
</Typography>
<Typography mb={1} color="secondary">
Platform
</Typography>
<Typography mb={1} color="secondary">
Release Type
</Typography>
<Grid
container
direction="row"
rowSpacing={1}
sx={{
justifyContent: 'flex-start',
alignItems: 'baseline'
}}
>
<Grid size={{ xs: 4, md: 2 }}>
<Typography color="secondary">{LL.VERSION()}</Typography>
</Grid>
<Grid mb={1}>
<Typography mb={1}>
<Grid size={{ xs: 8, md: 10 }}>
<Typography>
{data.emsesp_version}
{data.build_flags && (
<Typography variant="caption">
@@ -250,49 +305,121 @@ const Version = () => {
</Typography>
)}
</Typography>
<Typography mb={1}>{getPlatform()}</Typography>
<Typography mb={1}>
{data.emsesp_version.includes('dev')
? LL.DEVELOPMENT()
: LL.STABLE()}
</Grid>
<Grid size={{ xs: 4, md: 2 }}>
<Typography color="secondary">{LL.PLATFORM()}</Typography>
</Grid>
<Grid size={{ xs: 8, md: 10 }}>
<Typography>
{getPlatform()}
<Typography variant="caption">
&nbsp; &#40;{data.psram ? '+PSRAM' : '-PSRAM'}&#41;
</Typography>
</Typography>
</Grid>
<Grid size={{ xs: 4, md: 2 }}>
<Typography color="secondary">{LL.RELEASE_TYPE()}</Typography>
</Grid>
<Grid size={{ xs: 8, md: 10 }}>
<FormControlLabel
disabled={!isDev}
control={
<Checkbox
sx={{
'&.Mui-checked': {
color: 'lightblue'
}
}}
/>
}
slotProps={{
typography: {
color: 'grey'
}
}}
checked={!isDev}
label={LL.STABLE()}
sx={{ '& .MuiSvgIcon-root': { fontSize: 16 } }}
/>
<FormControlLabel
disabled={isDev}
control={
<Checkbox
sx={{
'&.Mui-checked': {
color: 'lightblue'
}
}}
/>
}
slotProps={{
typography: {
color: 'grey'
}
}}
checked={isDev}
label={LL.DEVELOPMENT()}
sx={{ '& .MuiSvgIcon-root': { fontSize: 16 } }}
/>
</Grid>
</Grid>
<Typography sx={{ pb: 2 }} variant="h6" color="primary">
{LL.AVAILABLE_VERSION()}
</Typography>
{internetLive ? (
<>
<Grid container spacing={4}>
<Grid mb={1}>
<Typography mb={1} color="secondary">
{LL.STABLE()}
</Typography>
<Typography mb={1} color="secondary">
{LL.DEVELOPMENT()}
<Typography mt={2} mb={2} variant="h6" color="primary">
{LL.AVAILABLE_VERSION()}
</Typography>
<Grid
container
direction="row"
rowSpacing={1}
sx={{
justifyContent: 'flex-start',
alignItems: 'baseline'
}}
>
<Grid size={{ xs: 4, md: 2 }}>
<Typography color="secondary">{LL.STABLE()}</Typography>
</Grid>
<Grid size={{ xs: 8, md: 10 }}>
<Typography>
<Link target="_blank" href={STABLE_RELNOTES_URL} color="primary">
{latestVersion.name}
</Link>
{latestVersion.published_at && (
<Typography component="span" variant="caption">
&nbsp;(
{formatTimeAgo(new Date(latestVersion.published_at))})
</Typography>
)}
{!usingDevVersion && showButtons(false)}
</Typography>
</Grid>
<Grid mb={1}>
<Typography mb={1}>
{latestVersion}&nbsp;&nbsp;
<Link target="_blank" href={STABLE_RELNOTES_URL} color="primary">
(changelog)
</Link>
{!usingDevVersion && showButtons()}
</Typography>
<Typography mb={1}>
{latestDevVersion}&nbsp;&nbsp;
<Grid size={{ xs: 4, md: 2 }}>
<Typography color="secondary">{LL.DEVELOPMENT()}</Typography>
</Grid>
<Grid size={{ xs: 8, md: 10 }}>
<Typography>
<Link target="_blank" href={DEV_RELNOTES_URL} color="primary">
(changelog)
{latestDevVersion.name}
</Link>
{usingDevVersion && showButtons()}
{latestDevVersion.published_at && (
<Typography component="span" variant="caption">
&nbsp;(
{formatTimeAgo(new Date(latestDevVersion.published_at))})
</Typography>
)}
{showButtons(true)}
</Typography>
</Grid>
</Grid>
{upgradeAvailable ? (
<Typography color="warning">
<Typography mt={2} color="warning">
<InfoOutlinedIcon
color="warning"
sx={{ verticalAlign: 'middle', mr: 2 }}
@@ -300,7 +427,7 @@ const Version = () => {
{LL.UPGRADE_AVAILABLE()}
</Typography>
) : (
<Typography color="success">
<Typography mt={2} color="success">
<CheckIcon
color="success"
sx={{ verticalAlign: 'middle', mr: 2 }}
@@ -308,36 +435,29 @@ const Version = () => {
{LL.LATEST_VERSION()}
</Typography>
)}
{!data.emsesp_version.includes('dev') && !usingDevVersion && (
<Typography variant="caption">
<Button
sx={{ mt: 2 }}
variant="outlined"
color="primary"
size="small"
onClick={() => switchToDev()}
>
{LL.SWITCH_DEV()}
</Button>
</Typography>
)}
</>
) : (
<Typography mb={1} color="warning">
<Typography mt={2} color="warning">
<WarningIcon color="warning" sx={{ verticalAlign: 'middle', mr: 2 }} />
device cannot access internet
{LL.INTERNET_CONNECTION_REQUIRED()}
</Typography>
)}
{renderInstallDialog()}
{me.admin && (
<>
{renderInstallDialog()}
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.UPLOAD()}
</Typography>
<SingleUpload text={LL.UPLOAD_DROP_TEXT()} doRestart={doRestart} />
</>
)}
</Box>
</>
);
};
return (
<SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>
<SectionContent>{restarting ? <SystemMonitor /> : content()}</SectionContent>
);
};

View File

@@ -0,0 +1,17 @@
import { Tooltip, type TooltipProps, styled, tooltipClasses } from '@mui/material';
export const ButtonTooltip = styled(({ className, ...props }: TooltipProps) => (
<Tooltip {...props} placement="top" arrow classes={{ popper: className }} />
))(({ theme }) => ({
[`& .${tooltipClasses.arrow}`]: {
color: theme.palette.success.main
},
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: theme.palette.success.main,
color: 'rgba(0, 0, 0, 0.87)',
boxShadow: theme.shadows[1],
fontSize: 10
}
}));
export default ButtonTooltip;

View File

@@ -11,7 +11,7 @@ type MessageBoxLevel = 'warning' | 'success' | 'info' | 'error';
export interface MessageBoxProps extends BoxProps {
level: MessageBoxLevel;
message: string;
message?: string;
}
const LEVEL_ICONS: {
@@ -53,8 +53,8 @@ const MessageBox: FC<MessageBoxProps> = ({
{...rest}
>
<Icon />
<Typography sx={{ ml: 2, flexGrow: 1 }} variant="body1">
{message}
<Typography sx={{ ml: 2 }} variant="body1">
{message ?? ''}
</Typography>
{children}
</Box>

View File

@@ -7,3 +7,4 @@ export { default as SectionContent } from './SectionContent';
export { default as ButtonRow } from './ButtonRow';
export { default as MessageBox } from './MessageBox';
export { default as BlockNavigation } from './routing/BlockNavigation';
export { default as ButtonTooltip } from './ButtonTooltip';

View File

@@ -73,11 +73,6 @@ const LayoutMenu = () => {
>
<ListItemText
primary={LL.MODULES()}
primaryTypographyProps={{
fontWeight: '600',
mb: '2px',
color: 'lightblue'
}}
// secondary={
// LL.CUSTOMIZATIONS() +
// ', ' +
@@ -92,6 +87,13 @@ const LayoutMenu = () => {
// color: menuOpen ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0.5)'
// }}
sx={{ my: 0 }}
slotProps={{
primary: {
fontWeight: '600',
mb: '2px',
color: 'lightblue'
}
}}
/>
<KeyboardArrowDown
sx={{
@@ -132,7 +134,6 @@ const LayoutMenu = () => {
)}
</Box>
</List>
<List style={{ marginTop: `auto` }}>
<LayoutMenuItem
icon={AssessmentIcon}
@@ -158,7 +159,6 @@ const LayoutMenu = () => {
</ListItemButton>
</ListItem>
</List>
<Popover
id={id}
open={open}

View File

@@ -1,20 +1,15 @@
import RefreshIcon from '@mui/icons-material/Refresh';
import { Box, Button, CircularProgress, Typography } from '@mui/material';
import { Box, Button, CircularProgress } from '@mui/material';
import { MessageBox } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
interface FormLoaderProps {
message?: string;
errorMessage?: string;
onRetry?: () => void;
}
const FormLoader = ({
errorMessage,
onRetry,
message = 'Loading…'
}: FormLoaderProps) => {
const FormLoader = ({ errorMessage, onRetry }: FormLoaderProps) => {
const { LL } = useI18nContext();
if (errorMessage) {
@@ -22,6 +17,7 @@ const FormLoader = ({
<MessageBox my={2} level="error" message={errorMessage}>
{onRetry && (
<Button
sx={{ ml: 2 }}
startIcon={<RefreshIcon />}
variant="contained"
color="error"
@@ -38,9 +34,6 @@ const FormLoader = ({
<Box py={2}>
<CircularProgress size={100} />
</Box>
<Typography variant="h6" fontWeight={400} textAlign="center">
{message}
</Typography>
</Box>
);
};

View File

@@ -1,15 +1,11 @@
import { Box, CircularProgress, Typography } from '@mui/material';
import { Box, CircularProgress } from '@mui/material';
import type { Theme } from '@mui/material';
import { useI18nContext } from 'i18n/i18n-react';
interface LoadingSpinnerProps {
height?: number | string;
}
const LoadingSpinner = ({ height = '100%' }: LoadingSpinnerProps) => {
const { LL } = useI18nContext();
return (
<Box
display="flex"
@@ -26,9 +22,6 @@ const LoadingSpinner = ({ height = '100%' }: LoadingSpinnerProps) => {
})}
size={100}
/>
<Typography variant="h4" color="textSecondary">
{LL.LOADING()}&hellip;
</Typography>
</Box>
);
};

View File

@@ -2,5 +2,3 @@ export { default as RouterTabs } from './RouterTabs';
export { default as RequireAdmin } from './RequireAdmin';
export { default as RequireAuthenticated } from './RequireAuthenticated';
export { default as RequireUnauthenticated } from './RequireUnauthenticated';
export * from './useRouterTab';

View File

@@ -1,8 +0,0 @@
import { useMatch, useResolvedPath } from 'react-router';
export const useRouterTab = () => {
const routerTabPathMatch = useMatch(useResolvedPath(':tab').pathname);
const routerTab = routerTabPathMatch?.params?.tab || false;
return { routerTab } as const;
};

View File

@@ -10,7 +10,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import './dragNdrop.css';
const DragNdrop = ({ onFileSelected }) => {
const DragNdrop = ({ text, onFileSelected }) => {
const [file, setFile] = useState<File>();
const [dragged, setDragged] = useState(false);
const inputRef = useRef<HTMLInputElement | null>(null);
@@ -73,7 +73,7 @@ const DragNdrop = ({ onFileSelected }) => {
>
<div className="upload-info">
<CloudUploadIcon sx={{ marginRight: 4 }} color="primary" fontSize="large" />
<p>{LL.UPLOAD_DRAG()}</p>
<p>{text}</p>
</div>
<input

View File

@@ -0,0 +1,23 @@
import {
Box,
LinearProgress,
type LinearProgressProps,
Typography
} from '@mui/material';
export function LinearProgressWithLabel(
props: LinearProgressProps & { value: number }
) {
return (
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ width: '100%', mr: 1 }}>
<LinearProgress variant="determinate" {...props} />
</Box>
<Box sx={{ minWidth: 35 }}>
<Typography variant="body2" color="text.secondary">{`${Math.round(
props.value
)}%`}</Typography>
</Box>
</Box>
);
}

View File

@@ -2,13 +2,7 @@ import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel';
import {
Box,
Button,
LinearProgress,
type LinearProgressProps,
Typography
} from '@mui/material';
import { Box, Button, Typography } from '@mui/material';
import * as SystemApi from 'api/system';
@@ -16,23 +10,9 @@ import { useRequest } from 'alova/client';
import { useI18nContext } from 'i18n/i18n-react';
import DragNdrop from './DragNdrop';
import { LinearProgressWithLabel } from './LinearProgressWithLabel';
function LinearProgressWithLabel(props: LinearProgressProps & { value: number }) {
return (
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ width: '100%', mr: 1 }}>
<LinearProgress variant="determinate" {...props} />
</Box>
<Box sx={{ minWidth: 35 }}>
<Typography variant="body2" color="text.secondary">{`${Math.round(
props.value
)}%`}</Typography>
</Box>
</Box>
);
}
const SingleUpload = ({ doRestart }) => {
const SingleUpload = ({ text, doRestart }) => {
const [md5, setMd5] = useState<string>();
const [file, setFile] = useState<File>();
const { LL } = useI18nContext();
@@ -93,7 +73,7 @@ const SingleUpload = ({ doRestart }) => {
</Button>
</>
) : (
<DragNdrop onFileSelected={setFile} />
<DragNdrop text={text} onFileSelected={setFile} />
)}
{md5 && (

View File

@@ -187,7 +187,7 @@ const cz: Translation = {
COMPACT: 'Kompaktní',
DOWNLOAD_SETTINGS_TEXT: 'Vytvořte zálohu svého nastavení a konfigurace',
UPLOAD_TEXT: 'Nahrajte nový soubor firmwaru (.bin) nebo záložní soubor (.json)',
UPLOAD_DROP_TEXT: 'Přetáhněte soubor nebo klikněte sem',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Neočekávaná chyba, zkuste to prosím znovu',
TIME_SET: 'Čas nastaven',
MANAGE_USERS: 'Spravovat uživatele',
@@ -332,18 +332,27 @@ const cz: Translation = {
SPECIAL_FUNCTIONS: 'Speciální funkce',
WAIT_FIRMWARE: 'Firmware se nahrává a instaluje',
INSTALL_VERSION: 'Tímto se instalovat verze {0}. Jste si jistí?',
SWITCH_DEV: 'přepnout na vývojovou verzi',
UPGRADE_AVAILABLE: 'Je k dispozici aktualizace firmwaru!',
LATEST_VERSION: 'Používáte nejnovější verzi firmwaru.',
LATEST_VERSION: 'Používáte nejnovější verzi firmwaru',
PLEASE_WAIT: 'Prosím čekejte',
RESTARTING_PRE: 'Inicializace',
RESTARTING_POST: 'Příprava',
AUTO_SCROLL: 'Automatické rolování',
DASHBOARD: 'Dashboard',
NO_DATA: 'Žádná data nejsou k dispozici',
DASHBOARD_1: 'Přizpůsobte si dashboard označením EMS entit jako Oblíbené pomocí modulu Přizpůsobení',
DEVELOPER_MODE: 'Režim vývojáře',
UPGRADE: 'Upgrade' // TODO translate
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplikát',
UPGRADE: 'Upgrade',
DASHBOARD_1: 'Všechny aktivní entity EMS jsou označené jako oblíbené. Všechny vlastní entity, harmonogramy a externí sensory jsou zobrazeny níže.',
NO_DATA_1: 'Nebyly nalezeny žádné oblíbené entity. Použijte',
NO_DATA_2: 'modul sloužící k jejich výběru.',
NO_DATA_3: 'Pro zobrazení všech dostupných entit navštivte',
THIS_VERSION: 'Tato verze',
PLATFORM: 'Platforma',
RELEASE_TYPE: 'Typ sestavení',
REINSTALL: 'Přeinstalovat',
INTERNET_CONNECTION_REQUIRED: 'Pro automatickou kontrolu a instalaci aktualizací je třeba internetové připojení',
};
export default cz;

View File

@@ -187,7 +187,7 @@ const de: Translation = {
COMPACT: 'Kompakte Darstellung',
DOWNLOAD_SETTINGS_TEXT: 'Erstellen Sie eine Sicherung Ihrer Konfigurationen und Einstellungen',
UPLOAD_TEXT: 'Laden Sie eine neue Firmware-Datei (.bin) oder eine Sicherungsdatei (.json) hoch',
UPLOAD_DROP_TEXT: 'Klicken Sie hier, oder ziehen Sie eine Datei hierher.',
UPLOAD_DROP_TEXT: 'Legen Sie eine Firmware-Datei (.bin) ab oder klicken Sie hier',
ERROR: 'Unerwarteter Fehler, bitte versuchen Sie es erneut.',
TIME_SET: 'Zeit gesetzt',
MANAGE_USERS: 'Nutzerverwaltung',
@@ -332,18 +332,27 @@ const de: Translation = {
SPECIAL_FUNCTIONS: 'Sonderfunktionen',
WAIT_FIRMWARE: 'Die Firmware wird hochgeladen und installiert.',
INSTALL_VERSION: 'Dadurch wird die Version {0} heruntergeladen. Sind Sie sicher?',
SWITCH_DEV: 'Wechseln Sie zur Entwicklungsversion!',
UPGRADE_AVAILABLE: 'Es ist ein Firmware-Upgrade verfügbar.',
LATEST_VERSION: 'Sie verwenden die neueste Firmware-Version.',
LATEST_VERSION: 'Sie verwenden die neueste Firmware-Version',
PLEASE_WAIT: 'Bitte warten',
RESTARTING_PRE: 'Initialisierung',
RESTARTING_POST: 'Vorbereitung',
AUTO_SCROLL: 'Automatisches Scrollen',
DASHBOARD: 'Dashboard',
NO_DATA: 'Keine Daten verfügbar',
DASHBOARD_1: 'Passen Sie Ihr Dashboard an, indem Sie EMS-Entitäten mithilfe des Moduls „Anpassungen“ als Favorit markieren',
DEVELOPER_MODE: 'Entwicklermodus',
UPGRADE: 'Upgrade' // TODO translate
BYTES: 'Bytes',
BITMASK: 'Bit Maske',
DUPLICATE: 'Kopieren',
UPGRADE: 'Aktualisieren',
DASHBOARD_1: 'Alle EMS-Entitäten, die aktiv und als Favorit markiert sind, sowie alle benutzerdefinierten Entitäten, Zeitpläne und externen Sensordaten werden unten angezeigt.',
NO_DATA_1: 'Keine favorisierten EMS-Entitäten gefunden! Verwenden Sie das Modul',
NO_DATA_2: ', um sie zu markieren.',
NO_DATA_3: 'Um alle verfügbaren Entitäten anzuzeigen, gehen Sie zu',
THIS_VERSION: 'Diese Version',
PLATFORM: 'Plattform',
RELEASE_TYPE: 'Release Typ',
REINSTALL: 'Neu installieren',
INTERNET_CONNECTION_REQUIRED: 'Internetverbindung erforderlich für automatische Version-Überprüfung und -Aktualisierung',
};
export default de;

View File

@@ -187,7 +187,7 @@ const en: Translation = {
COMPACT: 'Compact',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings',
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)',
UPLOAD_DROP_TEXT: 'Drop file or click here',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here',
ERROR: 'Unexpected Error, please try again',
TIME_SET: 'Time set',
MANAGE_USERS: 'Manage Users',
@@ -332,18 +332,27 @@ const en: Translation = {
SPECIAL_FUNCTIONS: 'Special Functions',
WAIT_FIRMWARE: 'Firmware is uploading and installing',
INSTALL_VERSION: 'This will install version {0}. Are you sure?',
SWITCH_DEV: 'switch to the development version',
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!',
LATEST_VERSION: 'You are using the latest firmware version.',
LATEST_VERSION: 'You are using the latest firmware version',
PLEASE_WAIT: 'Please wait',
RESTARTING_PRE: 'Initializing',
RESTARTING_POST: 'Preparing',
AUTO_SCROLL: 'Auto Scroll',
DASHBOARD: 'Dashboard',
NO_DATA: 'No data available',
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module',
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.',
DEVELOPER_MODE: 'Developer Mode',
UPGRADE: 'Upgrade'
BYTES: 'Bytes',
BITMASK: 'Bit Mask',
DUPLICATE: 'Duplicate',
UPGRADE: 'Upgrade',
NO_DATA_1: 'No favorite EMS entities found yet. Use the',
NO_DATA_2: 'module to mark them.',
NO_DATA_3: 'To see all available entities go to',
THIS_VERSION: 'This Version',
PLATFORM: 'Platform',
RELEASE_TYPE: 'Release Type',
REINSTALL: 'Re-install',
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
};
export default en;

View File

@@ -187,7 +187,7 @@ const fr: Translation = {
COMPACT: 'Compact',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
UPLOAD_DROP_TEXT: 'Déposer le fichier ou cliquer ici',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Erreur inattendue, veuillez réessayer',
TIME_SET: 'Time set',
MANAGE_USERS: 'Gérer les utilisateurs',
@@ -332,18 +332,27 @@ const fr: Translation = {
SPECIAL_FUNCTIONS: 'Special Functions',
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
SWITCH_DEV: 'switch to the development version', // TODO translate
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version.', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
NO_DATA: 'No data available', // TODO translate
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module', // TODO translate
DEVELOPER_MODE: 'Developer Mode', // TODO translate
UPGRADE: 'Upgrade' // TODO translate
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplicate', // TODO translate
UPGRADE: 'Upgrade', // TODO translate
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
NO_DATA_2: 'module to mark them.', // TODO translate
NO_DATA_3: 'To see all available entities go to', // TODO translate
THIS_VERSION: 'This Version', // TODO translate
PLATFORM: 'Platform', // TODO translate
RELEASE_TYPE: 'Release Type', // TODO translate
REINSTALL: 'Re-install', // TODO translate
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
};
export default fr;

View File

@@ -187,7 +187,7 @@ const it: Translation = {
COMPACT: 'Compact',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
UPLOAD_DROP_TEXT: 'Trascina il file o clicca qui',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Errore Inaspettato, prego tenta ancora',
TIME_SET: 'Imposta Ora',
MANAGE_USERS: 'Gestione Utenti',
@@ -332,18 +332,27 @@ const it: Translation = {
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
SWITCH_DEV: 'switch to the development version', // TODO translate
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version.', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
NO_DATA: 'No data available', // TODO translate
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module', // TODO translate
DEVELOPER_MODE: 'Developer Mode', // TODO translate
UPGRADE: 'Upgrade' // TODO translate
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplicate', // TODO translate
UPGRADE: 'Upgrade', // TODO translate
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
NO_DATA_2: 'module to mark them.', // TODO translate
NO_DATA_3: 'To see all available entities go to', // TODO translate
THIS_VERSION: 'This Version', // TODO translate
PLATFORM: 'Platform', // TODO translate
RELEASE_TYPE: 'Release Type', // TODO translate
REINSTALL: 'Re-install', // TODO translate
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
};
export default it;

View File

@@ -72,7 +72,7 @@ const nl: Translation = {
TX_ISSUES: 'Tx bus probleem. Probeer een andere Tx verzendmodus',
DISCONNECTED: 'Niet verbonden',
EMS_SCAN: 'Weet je zeker dat je een volledige EMS bus scan uit wilt voeren?',
DATA_TRAFFIC: 'Data Traffic', // TODO translate
DATA_TRAFFIC: 'Dataverkeer',
EMS_DEVICE: 'EMS Apparaat',
SUCCESS: 'SUCCESS',
FAIL: 'MISLUKT',
@@ -115,7 +115,7 @@ const nl: Translation = {
READONLY: 'Activeer read-only modus (blokkeert alle outgaande EMS Tx schrijf commandos)',
UNDERCLOCK_CPU: 'Underclock CPU snelheid',
REMOTE_TIMEOUT: 'Remote timeout',
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature', // TODO translate
REMOTE_TIMEOUT_EN: 'Schakel de afstandsbediening uit bij ontbrekende kamertemperatuur',
HEATINGOFF: 'Start ketel met geforceerde verwarming uit',
MIN_DURATION: 'Wait time',
ENABLE_SHOWER_TIMER: 'Activeer Douche Timer (tijdmeting)',
@@ -174,7 +174,7 @@ const nl: Translation = {
FACTORY_RESET: 'Fabrieksinstellingen',
SYSTEM_FACTORY_TEXT: 'Gateway is gereset en start nu weer op met fabrieksinstellingen',
SYSTEM_FACTORY_TEXT_DIALOG: 'Weet je zeker dat je een reset naar fabrieksinstellingen uit wilt voeren?',
AVAILABLE_VERSION: 'Latest Available Versions', // TODO translate
AVAILABLE_VERSION: 'Nieuwste beschikbare versies',
STABLE: 'Stable',
DEVELOPMENT: 'Development',
UPTIME: 'Systeem Uptime',
@@ -185,9 +185,9 @@ const nl: Translation = {
FILESYSTEM: 'File System (Used / Free)',
BUFFER_SIZE: 'Max Buffer Size',
COMPACT: 'Compact',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
UPLOAD_DROP_TEXT: 'Sleep bestand hierheen of klik hier',
DOWNLOAD_SETTINGS_TEXT: 'Maak een back-up van uw configuratie en instellingen',
UPLOAD_TEXT: 'Upload een nieuw firmwarebestand (.bin) of een back-upbestand (.json)',
UPLOAD_DROP_TEXT: 'Sleep en firmware .bin bestand hierheen of klik hier',
ERROR: 'Onverwachte fout, probeer opnieuw',
TIME_SET: 'Tijd ingesteld',
MANAGE_USERS: 'Beheer Gebruikers',
@@ -319,31 +319,40 @@ const nl: Translation = {
SECURITY_1: 'Gebruikers toevoegen of verwijderen',
DOWNLOAD_UPLOAD_1: 'Download en upload instellingen en firmware',
MODULES: 'Module',
MODULES_1: 'Externe modules activeren of deactiveren', // TODO translate
MODULES_1: 'Externe modules activeren of deactiveren',
MODULES_UPDATED: 'Modules geüpdatet',
MODULES_DESCRIPTION: 'Klik op de module om EMS-ESP library modules te activeren of te deactiveren',
MODULES_NONE: 'Geen externe modules gedetecteerd',
RENAME: 'Hernoemen',
ENABLE_MODBUS: 'Activeer Modbus',
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
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
SWITCH_DEV: 'switch to the development version', // TODO translate
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version.', // TODO translate
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
NO_DATA: 'No data available', // TODO translate
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module', // TODO translate
DEVELOPER_MODE: 'Developer Mode', // TODO translate
UPGRADE: 'Upgrade' // TODO translate
VIEW_LOG: 'Log weergeven om problemen te diagnosticeren',
UPLOAD_DRAG: 'sleep hier een bestand en zet het neer of klik om er een te selecteren',
SERVICES: 'Services',
ALLVALUES: 'All waarden',
SPECIAL_FUNCTIONS: 'Speciale functies',
WAIT_FIRMWARE: 'Firmware wordt geüpload en geïnstalleerd',
INSTALL_VERSION: 'Hiermee wordt versie {0} geïnstalleerd. Weet je het zeker?',
UPGRADE_AVAILABLE: 'Er is een firmware-upgrade beschikbaar!',
LATEST_VERSION: 'U gebruikt de nieuwste firmwareversie',
PLEASE_WAIT: 'Een ogenblik geduld',
RESTARTING_PRE: 'Initialiseren',
RESTARTING_POST: 'Voorbereiding',
AUTO_SCROLL: 'Automatisch Scrollen',
DASHBOARD: 'Dashboard',
DEVELOPER_MODE: 'Ontwikkelaarsmodus',
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplicaat',
UPGRADE: 'Upgraden',
DASHBOARD_1: 'Alle EMS-entiteiten die actief zijn en als favoriet zijn gemarkeerd, plus alle aangepaste entiteiten en externe sensorgegevens worden hieronder weergegeven.',
NO_DATA_1: 'Er zijn nog geen favoriete EMS-entiteiten gevonden. Gebruik de',
NO_DATA_2: 'module om ze te markeren.',
NO_DATA_3: 'Om alle beschikbare entiteiten te zien, ga naar',
THIS_VERSION: 'Deze Versie',
PLATFORM: 'Platform',
RELEASE_TYPE: 'Release Typ',
REINSTALL: 'Opnieuw Installeren',
INTERNET_CONNECTION_REQUIRED: 'Internetverbinding vereist voor automatische versiecontrole en -upgrade',
};
export default nl;
export default nl;

View File

@@ -187,7 +187,7 @@ const no: Translation = {
COMPACT: 'Komprimere',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
UPLOAD_DROP_TEXT: 'Slipp fil eller klikk her',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Ukjent feil, prøv igjen',
TIME_SET: 'Still in tid',
MANAGE_USERS: 'Administrer Brukere',
@@ -332,18 +332,27 @@ const no: Translation = {
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
SWITCH_DEV: 'switch to the development version', // TODO translate
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version.', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
NO_DATA: 'No data available', // TODO translate
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module', // TODO translate
DEVELOPER_MODE: 'Developer Mode', // TODO translate
UPGRADE: 'Upgrade' // TODO translate
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplicate', // TODO translate
UPGRADE: 'Upgrade', // TODO translate
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
NO_DATA_2: 'module to mark them.', // TODO translate
NO_DATA_3: 'To see all available entities go to', // TODO translate
THIS_VERSION: 'This Version', // TODO translate
PLATFORM: 'Platform', // TODO translate
RELEASE_TYPE: 'Release Type', // TODO translate
REINSTALL: 'Re-install', // TODO translate
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
};
export default no;

View File

@@ -187,7 +187,7 @@ const pl: BaseTranslation = {
COMPACT: 'Kompaktowy',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
UPLOAD_DROP_TEXT: 'Przeciągnij tutaj plik lub kliknij',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!',
TIME_SET: 'Zegar został ustawiony.',
MANAGE_USERS: 'Zarządzanie użytkownikami',
@@ -332,18 +332,27 @@ const pl: BaseTranslation = {
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
SWITCH_DEV: 'switch to the development version', // TODO translate
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version.', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
NO_DATA: 'No data available', // TODO translate
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module', // TODO translate
DEVELOPER_MODE: 'Developer Mode', // TODO translate
UPGRADE: 'Upgrade' // TODO translate
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplicate', // TODO translate
UPGRADE: 'Upgrade', // TODO translate
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
NO_DATA_2: 'module to mark them.', // TODO translate
NO_DATA_3: 'To see all available entities go to', // TODO translate
THIS_VERSION: 'This Version', // TODO translate
PLATFORM: 'Platform', // TODO translate
RELEASE_TYPE: 'Release Type', // TODO translate
REINSTALL: 'Re-install', // TODO translate
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading', // TODO translate
};
export default pl;

View File

@@ -57,7 +57,7 @@ const sk: Translation = {
OFFSET: 'Ofset',
FACTOR: 'Faktor',
FREQ: 'Frekvencia',
DUTY_CYCLE: 'Duty Cycle',
DUTY_CYCLE: 'Pracovný cyklus',
UNIT: 'UoM',
STARTVALUE: 'Počiatočná hodnota',
WARN_GPIO: 'Upozornenie: Buďte opatrní pri priraďovaní GPIO!',
@@ -75,7 +75,7 @@ const sk: Translation = {
DATA_TRAFFIC: 'Dátová prevádzka',
EMS_DEVICE: 'EMS zariadenie',
SUCCESS: 'ÚSPEŠNÉ',
FAIL: 'ZLÝHANIE',
FAIL: 'ZLYHANIE',
QUALITY: 'KVALITA',
SCAN: 'Scan',
STATUS_NAMES: [
@@ -114,10 +114,10 @@ const sk: Translation = {
BYPASS_TOKEN: 'Vynechajte autorizáciu prístupového tokenu pri volaniach API',
READONLY: 'Povoliť režim len na čítanie (blokuje všetky odchádzajúce príkazy EMS Tx Write)',
UNDERCLOCK_CPU: 'Podtaktovanie rýchlosti procesora',
REMOTE_TIMEOUT: 'Remote timeout',
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature', // TODO translate
REMOTE_TIMEOUT: 'Vzdialený časový limit',
REMOTE_TIMEOUT_EN: 'Deaktivujte diaľkové ovládanie pri chýbajúcej teplote v miestnosti',
HEATINGOFF: 'Spustiť kotol s vynúteným vykurovaním',
MIN_DURATION: 'Wait time',
MIN_DURATION: 'Čakacia doba',
ENABLE_SHOWER_TIMER: 'Povoliť časovač sprchovania',
ENABLE_SHOWER_ALERT: 'Povoliť upozornenie na sprchu',
TRIGGER_TIME: 'Čas spustenia',
@@ -127,7 +127,7 @@ const sk: Translation = {
BOOLEAN_FORMAT_API: 'Boolean formát API/MQTT',
ENUM_FORMAT: 'Enum formát API/MQTT',
INDEX: 'Index',
ENABLE_PARASITE: 'Povoliť 1-wire parazité napájanie DS18B20',
ENABLE_PARASITE: 'Povoliť 1-wire parazitné napájanie DS18B20',
LOGGING: 'Logovanie',
LOG_HEX: 'Záznam telegramov EMS v hexadecimálnej sústave',
ENABLE_SYSLOG: 'Povoliť Syslog',
@@ -174,20 +174,20 @@ const sk: Translation = {
FACTORY_RESET: 'Továrenské nastavenia',
SYSTEM_FACTORY_TEXT: 'Zariadenie bolo obnovené z výroby a teraz sa reštartuje',
SYSTEM_FACTORY_TEXT_DIALOG: 'Naozaj chcete resetovať EMS-ESP na predvolené výrobné nastavenia?',
AVAILABLE_VERSION: 'Latest Available Versions', // TODO translate
AVAILABLE_VERSION: 'Najnovšie dostupné verzie',
STABLE: 'Stabilná',
DEVELOPMENT: 'Vývojárska',
UPTIME: 'Beh systému',
FREE_MEMORY: 'Voľné Memory',
FREE_MEMORY: 'Voľná pamäť',
PSRAM: 'PSRAM (Veľkosť / Voľné)',
FLASH: 'Flash chip (Veľkosť , Rýchlosť)',
APPSIZE: 'Applikácia (Oddiel: Použité / Voľné)',
APPSIZE: 'Aplikácia (Oddiel: Použité / Voľné)',
FILESYSTEM: 'Súborový systém (Použité / Voľné)',
BUFFER_SIZE: 'Buffer-max.veľkosť',
BUFFER_SIZE: 'Buffer-max. veľkosť',
COMPACT: 'Kompaktné',
DOWNLOAD_SETTINGS_TEXT: 'Vytvorte zálohu svojej konfigurácie a nastavení',
UPLOAD_TEXT: 'Nahrajte nový súbor firmvéru (.bin) alebo súbor zálohy (.json)',
UPLOAD_DROP_TEXT: 'Potiahnúť a pripnúť súbor alebo kliknúť sem',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Neočakávaná chyba, prosím skúste to znova',
TIME_SET: 'Nastavený čas',
MANAGE_USERS: 'Správa používateľov',
@@ -313,11 +313,11 @@ const sk: Translation = {
UNCHANGED: 'Nezmenené',
ALWAYS: 'Vždy',
ACTIVITY: 'Aktivita',
CONFIGURE: 'Konfiguracia {0}',
CONFIGURE: 'Konfigurácia {0}',
SYSTEM_MEMORY: 'Systémová pamäť',
APPLICATION_SETTINGS_1: 'Zmeniť nastavenia aplikácie EMS-ESP',
SECURITY_1: 'Pridať, alebo odstrániť použivateľov',
DOWNLOAD_UPLOAD_1: 'Stiahnúť a nahrať nastavenia a firmware',
SECURITY_1: 'Pridať, alebo odstrániť používateľov',
DOWNLOAD_UPLOAD_1: 'Stiahnuť a nahrať nastavenia a firmware',
MODULES: 'Moduly',
MODULES_1: 'Aktivujte alebo deaktivujte externé moduly',
MODULES_UPDATED: 'Aktualizované moduly',
@@ -332,18 +332,27 @@ const sk: Translation = {
SPECIAL_FUNCTIONS: 'Špeciálne funkcie',
WAIT_FIRMWARE: 'Firmvér sa nahráva a inštaluje',
INSTALL_VERSION: 'Týmto sa inštalovať verzia {0}. Si si istý?',
SWITCH_DEV: 'prejsť na vývojovú verziu',
UPGRADE_AVAILABLE: 'K dispozícii je aktualizácia firmvéru!',
LATEST_VERSION: 'Používate poslednú verziu firmvéru.',
LATEST_VERSION: 'Používate poslednú verziu firmvéru',
PLEASE_WAIT: 'Čakajte prosím',
RESTARTING_PRE: 'Prebieha inicializácia',
RESTARTING_POST: 'Príprava',
AUTO_SCROLL: 'Automatické rolovanie',
DASHBOARD: 'Panel',
NO_DATA: 'Nie sú k dispozícii žiadne údaje',
DASHBOARD_1: 'Prispôsobte si svoj informačný panel tak, že označíte entity EMS ako Obľúbené pomocou modulu Prispôsobenia',
DEVELOPER_MODE: 'Developer Mode', // TODO translate
UPGRADE: 'Upgrade' // TODO translate
DEVELOPER_MODE: 'Režim vývojára',
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplicitné',
UPGRADE: 'Inovovať',
DASHBOARD_1: 'Všetky entity EMS, ktoré sú aktívne a označené ako obľúbené, plus všetky vlastné entity, plány a údaje externých senzorov sú zobrazené nižšie.',
NO_DATA_1: 'Nenašli sa žiadne obľúbené entity EMS. Použite',
NO_DATA_2: 'modul na ich označenie.',
NO_DATA_3: 'Ak chcete zobraziť všetky dostupné entity, prejdite na',
THIS_VERSION: 'Táto verzia',
PLATFORM: 'Platforma',
RELEASE_TYPE: 'Typ vydania',
REINSTALL: 'Preinštalovať',
INTERNET_CONNECTION_REQUIRED: 'Internetové pripojenie je potrebné pre automatickú kontrolu a aktualizáciu',
};
export default sk;

View File

@@ -5,8 +5,8 @@ const sv: Translation = {
RETRY: 'Försök igen',
LOADING: 'Laddar',
IS_REQUIRED: '{0} Krävs',
SIGN_IN: 'Logga In',
SIGN_OUT: 'Logga Ut',
SIGN_IN: 'Logga in',
SIGN_OUT: 'Logga ut',
USERNAME: 'Användarnamn',
PASSWORD: 'Lösenord',
SU_PASSWORD: 'su Lösenord',
@@ -29,7 +29,7 @@ const sv: Translation = {
FAVORITES: "Favoriter",
DEVICE_DETAILS: 'Enhetsdetaljer',
ID_OF: '{0}-ID',
DEVICE: 'Enhets',
DEVICE: 'Enhet',
PRODUCT: 'Produkt',
VERSION: 'Version',
BRAND: 'Fabrikat',
@@ -37,28 +37,28 @@ const sv: Translation = {
VALUE: '{{värde|Värde}}',
DEVICES: 'Enheter',
SENSORS: 'Sensorer',
RUN_COMMAND: 'Kör Kommando',
CHANGE_VALUE: 'Ändra Värde',
RUN_COMMAND: 'Kör kommando',
CHANGE_VALUE: 'Ändra värde',
CANCEL: 'Avbryt',
RESET: 'Nollställ',
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
UPDATE: 'Update', // TODO translate
EXECUTE: 'Execute', // TODO translate
APPLY_CHANGES: 'Utför ändringar ({0})',
UPDATE: 'Uppdatera',
EXECUTE: 'Utför',
REMOVE: 'Ta bort',
PROBLEM_UPDATING: 'Problem vid uppdatering',
PROBLEM_LOADING: 'Problem vid hämtning',
ANALOG_SENSOR: 'Analog Sensor',
ANALOG_SENSORS: 'Analoga Sensorer',
ANALOG_SENSOR: 'analog sensor',
ANALOG_SENSORS: 'Analoga sensorer',
SETTINGS: 'Inställningar',
UPDATED_OF: '{0} Uppdaterad',
UPDATE_OF: '{0} Uppdatera',
REMOVED_OF: '{0} Raderad',
DELETION_OF: '{0} Radering',
OFFSET: 'Kompensering',
OFFSET: 'Offset',
FACTOR: 'Faktor',
FREQ: 'Frekvens',
DUTY_CYCLE: 'Duty Cycle',
UNIT: 'UoM',
DUTY_CYCLE: 'Pulskvot',
UNIT: 'Måttenhet',
STARTVALUE: 'Startvärde',
WARN_GPIO: 'Varning: Var försiktig vid aktivering av GPIO!',
EDIT: 'Ändra',
@@ -71,9 +71,9 @@ const sv: Translation = {
CONNECTED: 'Ansluten',
TX_ISSUES: 'Sändfel - Prova ett annat TX-läge',
DISCONNECTED: 'Nedkopplad',
EMS_SCAN: 'Är du säker att du vill initiera en full genomsökning av EMS-bussen?',
DATA_TRAFFIC: 'Data Traffic', // TODO translate
EMS_DEVICE: 'EMS Enhet',
EMS_SCAN: 'Är du säker att du vill starta en full genomsökning av EMS-bussen?',
DATA_TRAFFIC: 'Datatrafik',
EMS_DEVICE: 'EMS enhet',
SUCCESS: 'Lyckades',
FAIL: 'Misslyckades',
QUALITY: 'Kvalitet',
@@ -92,8 +92,8 @@ const sv: Translation = {
NUM_SECONDS: '{num} sekund{{er}}',
NUM_HOURS: '{num} timmar',
NUM_MINUTES: '{num} minut{{er}}',
APPLICATION: 'Apliká',
CUSTOMIZATIONS: 'Anpassningr',
APPLICATION: 'Applikation',
CUSTOMIZATIONS: 'Anpassningar',
APPLICATION_RESTARTING: 'EMS-ESP startar om',
BOARD_PROFILE: 'Hårdvarutyp',
CUSTOM: 'Anpassa',
@@ -105,34 +105,34 @@ const sv: Translation = {
TX_MODE: 'EMS Tx-läge',
HARDWARE: 'Hårdvara',
EMS_BUS: '{{BUSS|EMS-BUSS}}',
GENERAL_OPTIONS: 'Allmänna Inställningar',
GENERAL_OPTIONS: 'Allmänna inställningar',
LANGUAGE_ENTITIES: 'Språk (för entiteter)',
HIDE_LED: 'Inaktivera LED',
ENABLE_TELNET: 'Aktivera Telnet',
ENABLE_ANALOG: 'Aktivera Analoga Sensorer',
CONVERT_FAHRENHEIT: 'Konvertera temperaturer till Fahrenheit',
BYPASS_TOKEN: 'Inaktivera Token-autensiering för API-anrop',
READONLY: 'Aktivera read-only (blockerar alla utgående skrivkommandon mot EMS-bussen)',
BYPASS_TOKEN: 'Inaktivera Token-autentisiering för API-anrop',
READONLY: 'Aktivera skrivskydd (blockerar alla utgående skrivkommandon mot EMS-bussen)',
UNDERCLOCK_CPU: 'Nedklocka Processorhastighet',
REMOTE_TIMEOUT: 'Remote timeout',
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature', // TODO translate
HEATINGOFF: 'Start boiler with forced heating off', // TODO translate
MIN_DURATION: 'Wait time',
REMOTE_TIMEOUT_EN: 'Deaktivera remote vid missad rumstemperatur',
HEATINGOFF: 'Starta värmepump/panna med forcerad värme avstängd',
MIN_DURATION: 'Väntetid',
ENABLE_SHOWER_TIMER: 'Aktivera Dusch-timer',
ENABLE_SHOWER_ALERT: 'Aktivera Dusch-varning',
TRIGGER_TIME: 'Aktiveringstid',
COLD_SHOT_DURATION: 'Längd på kalldusch',
FORMATTING_OPTIONS: 'Formatteringsalternativ',
FORMATTING_OPTIONS: 'Formateringsalternativ',
BOOLEAN_FORMAT_DASHBOARD: 'Bool-format Kontrollpanel',
BOOLEAN_FORMAT_API: 'Bool-format API/MQTT',
ENUM_FORMAT: 'Enum-format API/MQTT',
INDEX: 'Index',
ENABLE_PARASITE: 'Aktivera 1-wire parasitström',
LOGGING: 'Loggning',
LOG_HEX: 'Logga EMS-telegram i hexadecimal',
LOG_HEX: 'Logga EMS-telegram i hexadecimalformat',
ENABLE_SYSLOG: 'Aktivera Syslog',
LOG_LEVEL: 'Loggnivå',
MARK_INTERVAL: 'Markerings-interval',
MARK_INTERVAL: 'Markeringsintervall',
SECONDS: 'sekunder',
MINUTES: 'minuter',
HOURS: 'timmar',
@@ -140,25 +140,25 @@ const sv: Translation = {
RESTART_TEXT: 'EMS-ESP kräver en omstart för att applicera förändrade systeminställningar',
RESTART_CONFIRM: 'Är du säker på att du vill starta om EMS-ESP?',
COMMAND: 'Kommando',
CUSTOMIZATIONS_RESTART: 'Alla anpassningr har raderats. Startar om...',
CUSTOMIZATIONS_FULL: 'Antal valda enheter för högt. Vänligen spara i mindre antal åt gången.',
CUSTOMIZATIONS_SAVED: 'Anpassningar sparade',
CUSTOMIZATIONS_HELP_1: 'Välj en enhet och anpassa underenheter med hjälp av alternativen',
CUSTOMIZATIONS_RESTART: 'Alla anpassningar har raderats. Startar om...',
CUSTOMIZATIONS_FULL: 'För många valda enheter. Vänligen spara ett mindre antal åt gången.',
CUSTOMIZATIONS_SAVED: 'Anpassningarna är sparade',
CUSTOMIZATIONS_HELP_1: 'Välj en enhet och anpassa entiteter med hjälp av alternativen',
CUSTOMIZATIONS_HELP_2: 'Favorit',
CUSTOMIZATIONS_HELP_3: 'Inaktivera skrivningar',
CUSTOMIZATIONS_HELP_3: 'Skrivskyddad',
CUSTOMIZATIONS_HELP_4: 'Exkludera från MQTT & API',
CUSTOMIZATIONS_HELP_5: 'dölj från enheter',
CUSTOMIZATIONS_HELP_6: 'remove from memory',
CUSTOMIZATIONS_HELP_5: 'Dölj under Enheter',
CUSTOMIZATIONS_HELP_6: 'Ta bort',
SELECT_DEVICE: 'Välj en enhet',
SET_ALL: 'ställ in alla',
OPTIONS: 'Alternativ',
NAME: 'Namn',
CUSTOMIZATIONS_RESET: 'Är du säker på att du vill ta bort alla anpassningar inklusive inställningar för Temperatur och Analoga sensorer?',
CUSTOMIZATIONS_RESET: 'Är du säker på att du vill ta bort alla anpassningar, inklusive inställningar för Temperatursensorer och Analoga sensorer?',
SUPPORT_INFORMATION: 'Supportinformation',
HELP_INFORMATION_1: 'Besök Wikin för instruktioner för hur du kan konfigurera EMS-ESP',
HELP_INFORMATION_1: 'Besök Wikin för instruktioner om hur du kan konfigurera EMS-ESP',
HELP_INFORMATION_2: 'För community-support besök vår Discord-server',
HELP_INFORMATION_3: 'Önska en ny funktion eller rapportera en bugg',
HELP_INFORMATION_4: 'Bifoga din systeminformation för snabbare hantering när du rapporterar ett problem',
HELP_INFORMATION_4: 'Bifoga din supportinformation för snabbare hantering när du rapporterar ett problem',
UPLOAD: 'Uppladdning',
DOWNLOAD: '{{N|n|n}}edladdning',
INSTALL: 'Installera',
@@ -168,90 +168,90 @@ const sv: Translation = {
SYSTEM: 'System',
LOG_OF: '{0} Logg',
STATUS_OF: '{0} Status',
DOWNLOAD_UPLOAD: 'Nedladdning/Upp',
DOWNLOAD_UPLOAD: 'Nedladdning/Uppladdning',
CLOSE: 'Stäng',
USE: 'Använd',
FACTORY_RESET: 'Fabriksåterställning',
SYSTEM_FACTORY_TEXT: 'Enheten har blivit fabriksåterställd och startar nu om',
SYSTEM_FACTORY_TEXT_DIALOG: 'Är du säker att du vill fabriksåterställa enheten?',
AVAILABLE_VERSION: 'Latest Available Versions', // TODO translate
STABLE: 'Stable', // TODO translate
SYSTEM_FACTORY_TEXT_DIALOG: 'Är du säker att du vill fabriksåterställa enheten?',
AVAILABLE_VERSION: 'Senaste tillgängliga versioner',
STABLE: 'Stabil',
DEVELOPMENT: 'Utveckling',
UPTIME: 'Systemets Upptid',
FREE_MEMORY: 'Ledigt Memory',
UPTIME: 'Systemets upptid',
FREE_MEMORY: 'Ledigt minne',
PSRAM: 'PSRAM (Storlek / Ledigt)',
FLASH: 'Flashminne (Storlek , Hastighet)',
APPSIZE: 'Applikationer (Partition: Använt / Ledigt)',
FILESYSTEM: 'Filsystem (Använt / Ledigt)',
BUFFER_SIZE: 'Max Bufferstorlek',
COMPACT: 'Komprimera',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
UPLOAD_DROP_TEXT: 'Släpp fil eller klicka här',
ERROR: 'Okänt Fel, var god försök igen',
BUFFER_SIZE: 'Max bufferstorlek',
COMPACT: 'Komprimerad',
DOWNLOAD_SETTINGS_TEXT: 'Skapa en säkerhetskopia av din konfiguration och inställningar',
UPLOAD_TEXT: 'Ladda upp en ny firmwarefil (.bin) eller en säkerhetskopiafil (.json)',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Okänt fel, var god försök igen',
TIME_SET: 'Ställ in tid',
MANAGE_USERS: 'Användare',
IS_ADMIN: 'Admin',
USER_WARNING: 'Du måste ha minst en admin konfigurerad',
IS_ADMIN: 'Administratör',
USER_WARNING: 'Du måste ha minst en administratör konfigurerad',
ADD: 'Lägg till',
ACCESS_TOKEN_FOR: 'Access Token för',
ACCESS_TOKEN_TEXT: 'Nedan Token används med REST API-anrop som kräver auktorisering. Den kan skickas med antingen som en Bearer token i Authorization-headern eller i access_token URL query-parametern.',
GENERATING_TOKEN: 'Genererar token',
USER: 'Användare',
MODIFY: 'Ändra',
SU_TEXT: 'SU-användarens (super user) lösenord används för att signera autensierings-tokens samt för att aktivera administratörsprivilegier i Console-läge',
SU_TEXT: 'SU-användarens (super user) lösenord används för att signera autentisierings-tokens samt för att aktivera administratörsprivilegier i Console-läge',
NOT_ENABLED: 'Ej aktiv',
ERRORS_OF: '{0} fel',
DISCONNECT_REASON: 'Anledning till nedkoppling',
ENABLE_MQTT: 'Aktivera MQTT',
BROKER: 'Broker',
CLIENT: 'Client',
BASE_TOPIC: 'Base',
CLIENT: 'Klient',
BASE_TOPIC: 'Bas-topic',
OPTIONAL: 'valfritt',
FORMATTING: 'Formatering',
MQTT_FORMAT: 'Topic/Payload Format',
MQTT_NEST_1: 'Nestlat i en topic.',
MQTT_FORMAT: 'Topic/Payload format',
MQTT_NEST_1: 'Nästlat i en topic.',
MQTT_NEST_2: 'Som individuella topics',
MQTT_RESPONSE: 'Publish-kommando som ett `response` topic',
MQTT_PUBLISH_TEXT_1: 'Publicera single value topics vid värdeförändring',
MQTT_PUBLISH_TEXT_2: 'Publicera till kommando-topics (ioBroker)',
MQTT_PUBLISH_TEXT_3: 'Aktivera MQTT Discovery',
MQTT_PUBLISH_TEXT_4: 'Prefix för Discovery topics',
MQTT_PUBLISH_TEXT_5: 'Discovery type', // TODO translate
MQTT_PUBLISH_TEXT_4: 'Prefix för Discovery topics',
MQTT_PUBLISH_TEXT_5: 'Discoverytyp',
MQTT_PUBLISH_INTERVALS: 'Publiceringsintervall',
MQTT_INT_BOILER: 'Värmepump/panna',
MQTT_INT_THERMOSTATS: 'Termostater',
MQTT_INT_SOLAR: 'Solpaneler',
MQTT_INT_MIXER: 'Blandningsventiler',
MQTT_INT_WATER: 'Water Modules', // TODO translate
MQTT_INT_WATER: 'Varmvattenmoduler',
MQTT_QUEUE: 'MQTT-kö',
DEFAULT: 'Standard',
MQTT_ENTITY_FORMAT: 'Entitets-ID format',
MQTT_ENTITY_FORMAT_0: 'Singel-instans, långt namn(v3.4)',
MQTT_ENTITY_FORMAT_1: 'Singel-instans, kort name',
MQTT_ENTITY_FORMAT_2: 'Multi-instans, kort name',
MQTT_ENTITY_FORMAT_1: 'Singel-instans, kort namn',
MQTT_ENTITY_FORMAT_2: 'Multi-instans, kort namn',
MQTT_CLEAN_SESSION: 'Använd "Clean Session"-flaggan',
MQTT_RETAIN_FLAG: 'Använd "Always Retain"-flaggan',
INACTIVE: 'Inaktiv',
ACTIVE: 'Aktiv',
UNKNOWN: 'Okänt',
SET_TIME: 'Ställ in klockan',
SET_TIME_TEXT: 'Ange lokal datum och tid nedan för att ställa in klockan',
SET_TIME_TEXT: 'Ange lokalt datum och tid nedan för att ställa in klockan',
LOCAL_TIME: 'Tid (lokal)',
UTC_TIME: 'Tid (UTC)',
ENABLE_NTP: 'Aktivera NTP',
NTP_SERVER: 'NTP-server',
TIME_ZONE: 'Tidszon',
ACCESS_POINT: 'Accesspunkt',
AP_PROVIDE: 'Aktivera Accesspunkt',
AP_PROVIDE: 'Aktivera accesspunkt',
AP_PROVIDE_TEXT_1: 'alltid',
AP_PROVIDE_TEXT_2: 'när WiFi är nedkopplat',
AP_PROVIDE_TEXT_3: 'aldrig',
AP_PREFERRED_CHANNEL: 'Kanal',
AP_HIDE_SSID: 'Göm SSID',
AP_CLIENTS: 'AP-klienter',
AP_MAX_CLIENTS: 'Max Klienter',
AP_LOCAL_IP: 'Lokalt IP',
AP_MAX_CLIENTS: 'Max antal klienter',
AP_LOCAL_IP: 'Lokal IP-adress',
NETWORK_SCAN: 'Sök efter WiFi-nätverk',
IDLE: 'Vilande',
LOST: 'Förlorad',
@@ -260,90 +260,99 @@ const sv: Translation = {
NETWORK_SCANNER: 'Hittade nätverk',
NETWORK_NO_WIFI: 'Inga WiFi-nätverk hittades',
NETWORK_BLANK_SSID: 'lämna blankt för att inaktivera WiFi',
NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate
TX_POWER: 'Tx Effekt',
NETWORK_BLANK_BSSID: 'lämna blankt för att bara använda SSID',
TX_POWER: 'Tx effekt',
HOSTNAME: 'Värdnamn',
NETWORK_DISABLE_SLEEP: 'Inaktivera sömnläge',
NETWORK_DISABLE_SLEEP: 'Inaktivera sovläge',
NETWORK_LOW_BAND: 'Använd lägre bandbredd',
NETWORK_USE_DNS: 'Aktivera mDNS-tjänsten',
NETWORK_ENABLE_CORS: 'Aktivera CORS',
NETWORK_CORS_ORIGIN: 'CORS origin',
NETWORK_FIXED_IP: 'Använd statiskt IP',
NETWORK_FIXED_IP: 'Använd statisk IP-adress',
NETWORK_GATEWAY: 'Gateway',
NETWORK_SUBNET: 'Subnätmask',
NETWORK_DNS: 'DNS-Server',
ADDRESS_OF: '{0} Adress',
ADMINISTRATOR: 'Administrator',
ADMINISTRATOR: 'Administratör',
GUEST: 'Gäst',
NEW: 'Ny',
NEW: 'ny',
NEW_NAME_OF: 'Byt namn {0}',
ENTITY: 'Entitet',
MIN: 'min',
MAX: 'max',
BLOCK_NAVIGATE_1: 'You have unsaved changes', // TODO translate
BLOCK_NAVIGATE_2: 'If you navigate to a different page, your unsaved changes will be lost. Are you sure you want to leave this page?', // TODO translate
STAY: 'Stay', // TODO translate
LEAVE: 'Leave', // TODO translate
SCHEDULER: 'Scheduler', // TODO translate
SCHEDULER_HELP_1: 'Automate commands by adding scheduled events below. Set a unique Name to enable/disable activation via API/MQTT', // TODO translate
SCHEDULER_HELP_2: 'Use 00:00 to trigger once on start-up', // TODO translate
SCHEDULE: 'Schedule', // TODO translate
TIME: 'Time', // TODO translate
TIMER: 'Timer', // TODO translate
ONCHANGE: ' förändring',
CONDITION: 'Skick',
BLOCK_NAVIGATE_1: 'Du har osparade ändringar',
BLOCK_NAVIGATE_2: 'Om du navigerar till en annan sida, kommer dina osparade ändringar förloras. Är du säker på att du vill lämna den här sidan?',
STAY: 'Stanna',
LEAVE: 'Lämna',
SCHEDULER: 'Schemaläggning',
SCHEDULER_HELP_1: 'Automatisera kommandon genom att lägga till schemahändelser nedan. Ange ett unikt namn för att aktivera/avaktivera aktivering via API/MQTT',
SCHEDULER_HELP_2: 'Använd 00:00 för att trigga en gång vid uppstart',
SCHEDULE: 'schema',
TIME: 'Tid',
TIMER: 'Timer',
ONCHANGE: 'Vid förändring',
CONDITION: 'Villkor',
IMMEDIATE: 'Omedelbar',
SCHEDULE_UPDATED: 'Schedule updated', // TODO translate
SCHEDULE_TIMER_1: 'on startup', // TODO translate
SCHEDULE_TIMER_2: 'every minute', // TODO translate
SCHEDULE_TIMER_3: 'every hour', // TODO translate
CUSTOM_ENTITIES: 'Custom Entities', // TODO translate
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
WRITEABLE: 'Writeable', // TODO translate
SHOWING: 'Showing', // TODO translate
SEARCH: 'Search', // TODO translate
CERT: 'TLS root certificate (leave blank for insecure)', // TODO translate
SCHEDULE_UPDATED: 'Schema uppdaterat',
SCHEDULE_TIMER_1: 'vid uppstart',
SCHEDULE_TIMER_2: 'varje minut',
SCHEDULE_TIMER_3: 'varje timme',
CUSTOM_ENTITIES: 'Anpassade entiteter',
ENTITIES_HELP_1: 'Hämta anpassade entiteter från EMS bussen',
ENTITIES_UPDATED: 'Entiteter uppdaterade',
WRITEABLE: 'Skrivbara',
SHOWING: 'Visar',
SEARCH: 'Sök',
CERT: 'TLS rotcertifikat (lämna blankt för ingen säkerhet)',
ENABLE_TLS: 'Aktivera TLS',
ON: 'On', // TODO translate
OFF: 'Off', // TODO translate
POLARITY: 'Polarity', // TODO translate
ACTIVEHIGH: 'Active High', // TODO translate
ACTIVELOW: 'Active Low', // TODO translate
UNCHANGED: 'Unchanged', // TODO translate
ALWAYS: 'Always', // TODO translate
ACTIVITY: 'Activity', // TODO translate
CONFIGURE: 'Configure {0}', // TODO translate
SYSTEM_MEMORY: 'System Memory', // TODO translate
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
MODULES: 'Module', // TODO translate
ON: '',
OFF: 'Av',
POLARITY: 'Polaritet',
ACTIVEHIGH: 'Aktivt hög',
ACTIVELOW: 'Aktivt låg',
UNCHANGED: 'Oändrad',
ALWAYS: 'Alltid',
ACTIVITY: 'Aktivitet',
CONFIGURE: 'Konfigurera {0}',
SYSTEM_MEMORY: 'Systemminne',
APPLICATION_SETTINGS_1: 'Ändra EMS-ESP Applikationsinställningar',
SECURITY_1: 'Lägg till eller ta bort användare',
DOWNLOAD_UPLOAD_1: 'Ladda ner eller ladda upp inställningar och firmware',
MODULES: 'Moduler',
MODULES_1: 'Aktivera eller avaktivera externa moduler',
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename', // TODO translate
MODULES_UPDATED: 'Moduler updaterade',
MODULES_DESCRIPTION: 'Klicka på modulen för att aktivera eller deaktivera EMS-ESP moduler',
MODULES_NONE: 'Inga externa moduler upptäckta',
RENAME: 'Byt namn',
ENABLE_MODBUS: 'Aktivera Modbus',
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
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
SWITCH_DEV: 'switch to the development version', // TODO translate
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version.', // TODO translate
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
NO_DATA: 'No data available', // TODO translate
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module', // TODO translate
DEVELOPER_MODE: 'Developer Mode', // TODO translate
UPGRADE: 'Upgrade' // TODO translate
VIEW_LOG: 'Titta i loggen för att felsöka problem',
UPLOAD_DRAG: 'dra och släpp en fil här eller klicka för att välja en',
SERVICES: 'Tjänster',
ALLVALUES: 'Alla värden',
SPECIAL_FUNCTIONS: 'Specialfunktioner',
WAIT_FIRMWARE: 'Firmware laddas upp och installeras',
INSTALL_VERSION: 'Det här kommer installera version {0}. Är du säker?',
UPGRADE_AVAILABLE: 'Det finns en tillgänglig firmwareupgradering!',
LATEST_VERSION: 'Du använder den senaste firmwareversionen.',
PLEASE_WAIT: 'Var god vänta',
RESTARTING_PRE: 'Initialiserar',
RESTARTING_POST: 'Förbereder',
AUTO_SCROLL: 'Autoskrolla',
DASHBOARD: 'Kontrollpanel',
DEVELOPER_MODE: 'Utvecklarläge',
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Dublett',
UPGRADE: 'Uppgradera',
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
NO_DATA_2: 'module to mark them.', // TODO translate
NO_DATA_3: 'To see all available entities go to', // TODO translate
THIS_VERSION: 'This Version', // TODO translate
PLATFORM: 'Platform', // TODO translate
RELEASE_TYPE: 'Release Type', // TODO translate
REINSTALL: 'Re-install', // TODO translate
INTERNET_CONNECTION_REQUIRED: 'Internetanslutning krävs för automatisk version kontroll och uppdatering',
};
export default sv;

View File

@@ -187,7 +187,7 @@ const tr: Translation = {
COMPACT: 'Sıkışık',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
UPLOAD_DROP_TEXT: 'Buraya tıklayın yada dosyayı sürükleyip bırakın',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Beklenemedik hata, lütfen tekrar deneyin.',
TIME_SET: 'Zaman ayarı',
MANAGE_USERS: 'Kullanıcıları yönet',
@@ -332,7 +332,6 @@ const tr: Translation = {
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
SWITCH_DEV: 'switch to the development version', // TODO translate
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version.', // TODO translate
PLEASE_WAIT: 'Please wait', // TODO translate
@@ -340,10 +339,20 @@ const tr: Translation = {
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
NO_DATA: 'No data available', // TODO translate
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module', // TODO translate
DEVELOPER_MODE: 'Developer Mode', // TODO translate
UPGRADE: 'Upgrade' // TODO translate
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplicate', // TODO translate
UPGRADE: 'Upgrade', // TODO translate
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
NO_DATA_2: 'module to mark them.', // TODO translate
NO_DATA_3: 'To see all available entities go to', // TODO translate
THIS_VERSION: 'This Version', // TODO translate
PLATFORM: 'Platform', // TODO translate
RELEASE_TYPE: 'Release Type', // TODO translate
REINSTALL: 'Re-install', // TODO translate
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading', // TODO translate
};
export default tr;

View File

@@ -2,6 +2,15 @@ import type { busConnectionStatus } from 'app/main/types';
import type { NetworkConnectionStatus } from './network';
export enum SystemStatusCodes {
SYSTEM_STATUS_NORMAL = 0,
SYSTEM_STATUS_PENDING_UPLOAD = 1,
SYSTEM_STATUS_UPLOADING = 100,
SYSTEM_STATUS_ERROR_UPLOAD = 3,
SYSTEM_STATUS_PENDING_RESTART = 4,
SYSTEM_STATUS_RESTART_REQUESTED = 5
}
export interface SystemStatus {
emsesp_version: string;
bus_status: busConnectionStatus;
@@ -11,6 +20,7 @@ export interface SystemStatus {
num_sensors: number;
num_analogs: number;
ntp_status: number;
ntp_time?: string;
mqtt_status: boolean;
ap_status: boolean;
network_status: NetworkConnectionStatus;
@@ -40,7 +50,7 @@ export interface SystemStatus {
model: string;
has_loader: boolean;
has_partition: boolean;
status: string;
status: number; // SystemStatusCodes which matches SYSTEM_STATUS in System.h
temperature?: number;
}

View File

@@ -1,7 +1,9 @@
import { useEffect, useRef } from 'react';
const DEFAULT_DELAY = 3000;
// adapted from https://www.joshwcomeau.com/snippets/react-hooks/use-interval/
export const useInterval = (callback: () => void, delay: number) => {
export const useInterval = (callback: () => void, delay: number = DEFAULT_DELAY) => {
const intervalRef = useRef<number | null>(null);
const savedCallback = useRef<() => void>(callback);
@@ -11,14 +13,12 @@ export const useInterval = (callback: () => void, delay: number) => {
useEffect(() => {
const tick = () => savedCallback.current();
if (typeof delay === 'number') {
intervalRef.current = window.setInterval(tick, delay);
return () => {
if (intervalRef.current !== null) {
window.clearInterval(intervalRef.current);
}
};
}
intervalRef.current = window.setInterval(tick, delay);
return () => {
if (intervalRef.current !== null) {
window.clearInterval(intervalRef.current);
}
};
}, [delay]);
return intervalRef;

File diff suppressed because it is too large Load Diff