This commit is contained in:
proddy
2024-03-17 19:08:03 +01:00
parent 2739712c5b
commit 9bf7fbfb2e
58 changed files with 631 additions and 646 deletions

View File

@@ -31,8 +31,8 @@
"@table-library/react-table-library": "4.1.7", "@table-library/react-table-library": "4.1.7",
"@types/imagemin": "^8.0.5", "@types/imagemin": "^8.0.5",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^20.11.27", "@types/node": "^20.11.28",
"@types/react": "^18.2.65", "@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22", "@types/react-dom": "^18.2.22",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"alova": "^2.17.1", "alova": "^2.17.1",
@@ -46,14 +46,14 @@
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-icons": "^5.0.1", "react-icons": "^5.0.1",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.22.3",
"react-toastify": "^10.0.4", "react-toastify": "^10.0.5",
"sockette": "^2.0.6", "sockette": "^2.0.6",
"typesafe-i18n": "^5.26.2", "typesafe-i18n": "^5.26.2",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"devDependencies": { "devDependencies": {
"@preact/compat": "^17.1.2", "@preact/compat": "^17.1.2",
"@preact/preset-vite": "^2.8.1", "@preact/preset-vite": "^2.8.2",
"@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0", "@typescript-eslint/parser": "^7.2.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
@@ -64,15 +64,15 @@
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-prettier": "alpha", "eslint-plugin-prettier": "alpha",
"eslint-plugin-react": "^7.34.0", "eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"preact": "^10.19.6", "preact": "^10.19.6",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"terser": "^5.29.1", "terser": "^5.29.2",
"vite": "^5.1.6", "vite": "^5.1.6",
"vite-plugin-imagemin": "^0.6.1", "vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^4.3.1" "vite-tsconfig-paths": "^4.3.2"
}, },
"packageManager": "yarn@4.1.1" "packageManager": "yarn@4.1.1"
} }

View File

@@ -28,7 +28,7 @@ const App: FC = () => {
<CustomTheme> <CustomTheme>
<AppRouting /> <AppRouting />
<ToastContainer <ToastContainer
position="bottom-left" position="bottom-right"
autoClose={3000} autoClose={3000}
hideProgressBar={false} hideProgressBar={false}
newestOnTop={false} newestOnTop={false}

View File

@@ -1,61 +1,63 @@
import { Navigate, Routes, Route } from 'react-router-dom'; import { Navigate, Routes, Route } from 'react-router-dom';
import Dashboard from './project/Dashboard';
import Help from './project/Help'; import Help from './project/Help';
import Settings from './project/Settings';
import type { FC } from 'react'; import type { FC } from 'react';
import { Layout, RequireAdmin } from 'components'; import { Layout, RequireAdmin } from 'components';
import Settings from 'framework/Settings';
import AccessPoint from 'framework/ap/AccessPoint'; import AccessPoint from 'framework/ap/AccessPoint';
import Mqtt from 'framework/mqtt/Mqtt'; import Mqtt from 'framework/mqtt/Mqtt';
import NetworkConnection from 'framework/network/NetworkConnection'; import NetworkConnection from 'framework/network/NetworkConnection';
import NetworkTime from 'framework/ntp/NetworkTime'; import NetworkTime from 'framework/ntp/NetworkTime';
import OTASettingsForm from 'framework/ota/OTASettingsForm';
import Security from 'framework/security/Security'; import Security from 'framework/security/Security';
import System from 'framework/system/System'; import Status from 'framework/system/Status';
import UploadDownload from 'framework/system/UploadDownload';
import ApplicationSettings from 'project/ApplicationSettings';
import CustomEntities from 'project/CustomEntities';
import Customization from 'project/Customization';
import Devices from 'project/Devices';
import Scheduler from 'project/Scheduler';
import Sensors from 'project/Sensors';
const AuthenticatedRouting: FC = () => ( const AuthenticatedRouting: FC = () => (
// const location = useLocation();
// const navigate = useNavigate();
// const handleApiResponseError = useCallback(
// (error: AxiosError) => {
// if (error.response && error.response.status === 401) {
// AuthenticationApi.storeLoginRedirect(location);
// navigate('/unauthorized');
// }
// return Promise.reject(error);
// },
// [location, navigate]
// );
// useEffect(() => {
// const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError);
// return () => AXIOS.interceptors.response.eject(axiosHandlerId);
// }, [handleApiResponseError]);
<Layout> <Layout>
<Routes> <Routes>
<Route path="/dashboard/*" element={<Dashboard />} /> <Route path="/devices/*" element={<Devices />} />
<Route path="/sensors/*" element={<Sensors />} />
<Route path="/customizations/*" element={<Customization />} />
<Route path="/scheduler/*" element={<Scheduler />} />
<Route path="/customentities/*" element={<CustomEntities />} />
{/* TODO only show the rest here if admin */}
<Route path="/status/*" element={<Status />} />
<Route <Route
path="/settings/*" path="settings/*"
element={ element={
<RequireAdmin> <RequireAdmin>
<Settings /> <Settings />
</RequireAdmin> </RequireAdmin>
} }
/> />
<Route path="/help/*" element={<Help />} /> <Route path="/settings/network/*" element={<NetworkConnection />} />
<Route path="/settings/ems-esp/*" element={<ApplicationSettings />} />
<Route path="/network/*" element={<NetworkConnection />} /> <Route path="/settings/ap/*" element={<AccessPoint />} />
<Route path="/ap/*" element={<AccessPoint />} /> <Route path="/settings/ntp/*" element={<NetworkTime />} />
<Route path="/ntp/*" element={<NetworkTime />} /> <Route path="/settings/mqtt/*" element={<Mqtt />} />
<Route path="/mqtt/*" element={<Mqtt />} /> <Route path="/settings/ota/*" element={<OTASettingsForm />} />
<Route <Route
path="/security/*" path="/settings/security/*"
element={ element={
<RequireAdmin> <RequireAdmin>
<Security /> <Security />
</RequireAdmin> </RequireAdmin>
} }
/> />
<Route path="/system/*" element={<System />} /> <Route path="/settings/upload/*" element={<UploadDownload />} />
<Route path="/help/*" element={<Help />} />
<Route path="/*" element={<Navigate to="/" />} /> <Route path="/*" element={<Navigate to="/" />} />
</Routes> </Routes>
</Layout> </Layout>

View File

@@ -32,7 +32,7 @@ export function fetchLoginRedirect(): Partial<Path> {
const signInSearch = getStorage().getItem(SIGN_IN_SEARCH); const signInSearch = getStorage().getItem(SIGN_IN_SEARCH);
clearLoginRedirect(); clearLoginRedirect();
return { return {
pathname: signInPathname || `/dashboard`, pathname: signInPathname || `/devices`,
search: (signInPathname && signInSearch) || undefined search: (signInPathname && signInSearch) || undefined
}; };
} }

View File

@@ -4,8 +4,7 @@ import type { FC } from 'react';
import type { RequiredChildrenProps } from 'utils'; import type { RequiredChildrenProps } from 'utils';
interface SectionContentProps extends RequiredChildrenProps { interface SectionContentProps extends RequiredChildrenProps {
title: string; title?: string;
titleGutter?: boolean;
id?: string; id?: string;
} }
@@ -13,7 +12,9 @@ const SectionContent: FC<SectionContentProps> = (props) => {
const { children, title, id } = props; const { children, title, id } = props;
return ( return (
<Paper id={id} sx={{ p: 2, m: 2 }}> <Paper id={id} sx={{ p: 2, m: 2 }}>
{title && (
<Divider sx={{ pb: 2, borderColor: 'primary.main', fontSize: 20, color: 'primary.main' }}>{title}</Divider> <Divider sx={{ pb: 2, borderColor: 'primary.main', fontSize: 20, color: 'primary.main' }}>{title}</Divider>
)}
{children} {children}
</Paper> </Paper>
); );

View File

@@ -1,6 +1,5 @@
import MenuIcon from '@mui/icons-material/Menu'; import MenuIcon from '@mui/icons-material/Menu';
import { AppBar, Box, IconButton, Toolbar, Typography } from '@mui/material'; import { AppBar, IconButton, Toolbar, Typography } from '@mui/material';
import LayoutAuthMenu from './LayoutAuthMenu';
import type { FC } from 'react'; import type { FC } from 'react';
export const DRAWER_WIDTH = 210; export const DRAWER_WIDTH = 210;
@@ -27,8 +26,6 @@ const LayoutAppBar: FC<LayoutAppBarProps> = ({ title, onToggleDrawer }) => (
<Typography variant="h6" noWrap component="div"> <Typography variant="h6" noWrap component="div">
{title} {title}
</Typography> </Typography>
<Box flexGrow={1} />
<LayoutAuthMenu />
</Toolbar> </Toolbar>
</AppBar> </AppBar>
); );

View File

@@ -1,165 +0,0 @@
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import PersonIcon from '@mui/icons-material/Person';
import {
Box,
Button,
Divider,
IconButton,
Popover,
Typography,
Avatar,
styled,
MenuItem,
TextField
} from '@mui/material';
import { useState, useContext } from 'react';
import type { TypographyProps } from '@mui/material';
import type { Locales } from 'i18n/i18n-types';
import type { FC, ChangeEventHandler } from 'react';
import { AuthenticatedContext } from 'contexts/authentication';
import DEflag from 'i18n/DE.svg';
import FRflag from 'i18n/FR.svg';
import GBflag from 'i18n/GB.svg';
import ITflag from 'i18n/IT.svg';
import NLflag from 'i18n/NL.svg';
import NOflag from 'i18n/NO.svg';
import PLflag from 'i18n/PL.svg';
import SKflag from 'i18n/SK.svg';
import SVflag from 'i18n/SV.svg';
import TRflag from 'i18n/TR.svg';
import { I18nContext } from 'i18n/i18n-react';
import { loadLocaleAsync } from 'i18n/i18n-util.async';
const ItemTypography = styled(Typography)<TypographyProps>({
maxWidth: '250px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
});
const LayoutAuthMenu: FC = () => {
const { me, signOut } = useContext(AuthenticatedContext);
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const { locale, LL, setLocale } = useContext(I18nContext);
const onLocaleSelected: ChangeEventHandler<HTMLInputElement> = async ({ target }) => {
const loc = target.value as Locales;
localStorage.setItem('lang', loc);
await loadLocaleAsync(loc);
setLocale(loc);
};
const handleClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const id = anchorEl ? 'app-menu-popover' : undefined;
return (
<>
<TextField
name="locale"
InputProps={{ style: { fontSize: 10 } }}
variant="outlined"
value={locale}
onChange={onLocaleSelected}
size="small"
select
>
<MenuItem key="de" value="de">
<img src={DEflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;DE
</MenuItem>
<MenuItem key="en" value="en">
<img src={GBflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;EN
</MenuItem>
<MenuItem key="fr" value="fr">
<img src={FRflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;FR
</MenuItem>
<MenuItem key="it" value="it">
<img src={ITflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;IT
</MenuItem>
<MenuItem key="nl" value="nl">
<img src={NLflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;NL
</MenuItem>
<MenuItem key="no" value="no">
<img src={NOflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;NO
</MenuItem>
<MenuItem key="pl" value="pl">
<img src={PLflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;PL
</MenuItem>
<MenuItem key="sk" value="sk">
<img src={SKflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;SK
</MenuItem>
<MenuItem key="sv" value="sv">
<img src={SVflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;SV
</MenuItem>
<MenuItem key="tr" value="tr">
<img src={TRflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;TR
</MenuItem>
</TextField>
<IconButton
id="open-auth-menu"
sx={{ ml: 1, padding: 0 }}
aria-describedby={id}
color="inherit"
onClick={handleClick}
>
<AccountCircleIcon />
</IconButton>
<Popover
id="app-menu-popover"
sx={{ mt: 1 }}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center'
}}
>
<Box display="flex" flexDirection="row" alignItems="center" p={2}>
<Avatar sx={{ width: 80, height: 80 }}>
<PersonIcon fontSize="large" />
</Avatar>
<Box pl={2}>
<ItemTypography variant="h6">{me.username}</ItemTypography>
<ItemTypography variant="body1">
{me.admin ? LL.ADMIN() : LL.GUEST()}&nbsp;{LL.USER(2)}
</ItemTypography>
</Box>
</Box>
<Divider />
<Box p={1.5}>
<Button variant="outlined" fullWidth color="primary" onClick={() => signOut(true)}>
{LL.SIGN_OUT()}
</Button>
</Box>
</Popover>
</>
);
};
export default LayoutAuthMenu;

View File

@@ -1,6 +1,8 @@
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material'; import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
import { DRAWER_WIDTH } from './Layout'; import { DRAWER_WIDTH } from './Layout';
import LayoutMenu from './LayoutMenu'; import LayoutMenu from './LayoutMenu';
import type { FC } from 'react'; import type { FC } from 'react';
import { PROJECT_NAME } from 'api/env'; import { PROJECT_NAME } from 'api/env';

View File

@@ -1,53 +1,188 @@
import AccessTimeIcon from '@mui/icons-material/AccessTime'; import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import AssessmentIcon from '@mui/icons-material/Assessment';
import DashboardIcon from '@mui/icons-material/Dashboard'; import CategoryIcon from '@mui/icons-material/Category';
import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import ConstructionIcon from '@mui/icons-material/Construction';
import InfoIcon from '@mui/icons-material/Info'; import InfoIcon from '@mui/icons-material/Info';
import LockIcon from '@mui/icons-material/Lock'; import MoreTimeIcon from '@mui/icons-material/MoreTime';
import PersonIcon from '@mui/icons-material/Person';
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
import SensorsIcon from '@mui/icons-material/Sensors';
import SettingsIcon from '@mui/icons-material/Settings'; import SettingsIcon from '@mui/icons-material/Settings';
import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet'; import {
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; Divider,
import TuneIcon from '@mui/icons-material/Tune'; List,
import { Divider, List } from '@mui/material'; Box,
import { useContext } from 'react'; Button,
import type { FC } from 'react'; Popover,
Avatar,
MenuItem,
TextField,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText
} from '@mui/material';
import { useContext, useState } from 'react';
import type { Locales } from 'i18n/i18n-types';
import type { FC, ChangeEventHandler } from 'react';
import LayoutMenuItem from 'components/layout/LayoutMenuItem'; import LayoutMenuItem from 'components/layout/LayoutMenuItem';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import DEflag from 'i18n/DE.svg';
import FRflag from 'i18n/FR.svg';
import GBflag from 'i18n/GB.svg';
import ITflag from 'i18n/IT.svg';
import NLflag from 'i18n/NL.svg';
import NOflag from 'i18n/NO.svg';
import PLflag from 'i18n/PL.svg';
import SKflag from 'i18n/SK.svg';
import SVflag from 'i18n/SV.svg';
import TRflag from 'i18n/TR.svg';
import { I18nContext } from 'i18n/i18n-react';
import { loadLocaleAsync } from 'i18n/i18n-util.async';
const LayoutMenu: FC = () => { const LayoutMenu: FC = () => {
const authenticatedContext = useContext(AuthenticatedContext); const { me, signOut } = useContext(AuthenticatedContext);
const { LL } = useI18nContext(); const { locale, LL, setLocale } = useContext(I18nContext);
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const open = Boolean(anchorEl);
const id = anchorEl ? 'app-menu-popover' : undefined;
const onLocaleSelected: ChangeEventHandler<HTMLInputElement> = async ({ target }) => {
const loc = target.value as Locales;
localStorage.setItem('lang', loc);
await loadLocaleAsync(loc);
setLocale(loc);
};
const handleClick = (event: any) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return ( return (
<> <>
<List disablePadding component="nav"> <List component="nav">
<LayoutMenuItem icon={DashboardIcon} label={LL.DASHBOARD()} to={`/dashboard`} /> <LayoutMenuItem icon={CategoryIcon} label={LL.DEVICES()} to={`/devices`} />
<LayoutMenuItem <LayoutMenuItem icon={SensorsIcon} label={LL.SENSORS()} to={`/sensors`} />
icon={TuneIcon}
label={LL.SETTINGS_OF('')}
to={`/settings`}
disabled={!authenticatedContext.me.admin}
/>
<LayoutMenuItem icon={InfoIcon} label={LL.HELP_OF('')} to={`/help`} />
<Divider /> <Divider />
<LayoutMenuItem icon={ConstructionIcon} label={LL.CUSTOMIZATIONS()} to={`/customizations`} />
<LayoutMenuItem icon={MoreTimeIcon} label={LL.SCHEDULER()} to={`/scheduler`} />
<LayoutMenuItem icon={PlaylistAddIcon} label={LL.CUSTOM_ENTITIES(0)} to={`/customentities`} />
</List> </List>
<List disablePadding component="nav"> <List style={{ marginTop: `auto` }}>
<LayoutMenuItem icon={SettingsEthernetIcon} label={LL.NETWORK(0)} to="/network" /> <LayoutMenuItem icon={AssessmentIcon} label={LL.STATUS_OF('')} to="/status" />
<LayoutMenuItem icon={SettingsInputAntennaIcon} label={LL.ACCESS_POINT(0)} to="/ap" /> <LayoutMenuItem icon={SettingsIcon} label={LL.SETTINGS(0)} disabled={!me.admin} to="/settings" />
<LayoutMenuItem icon={AccessTimeIcon} label="NTP" to="/ntp" /> <LayoutMenuItem icon={InfoIcon} label={LL.HELP_OF('')} to={`/help`} />
<LayoutMenuItem icon={DeviceHubIcon} label="MQTT" to="/mqtt" />
<LayoutMenuItem
icon={LockIcon}
label={LL.SECURITY(0)}
to="/security"
disabled={!authenticatedContext.me.admin}
/>
<LayoutMenuItem icon={SettingsIcon} label={LL.SYSTEM(0)} to="/system" />
</List> </List>
<Divider />
<List>
<ListItem disablePadding onClick={handleClick}>
<ListItemButton>
<ListItemIcon>
<AccountCircleIcon />
</ListItemIcon>
<ListItemText>{me.username}</ListItemText>
</ListItemButton>
</ListItem>
</List>
<Popover
id={id}
sx={{ mt: 1 }}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center'
}}
>
<Box
p={2}
sx={{
borderRadius: 2,
border: '2px solid grey'
}}
>
<List>
<ListItem disablePadding>
<Avatar sx={{ bgcolor: '#b1395f', color: 'white' }}>
<PersonIcon />
</Avatar>
<ListItemText sx={{ pl: 2 }} primary={me.username} secondary={me.admin ? LL.ADMIN() : LL.GUEST()} />
</ListItem>
</List>
<Box p={2}>
<TextField
name="locale"
InputProps={{ style: { fontSize: 10 } }}
variant="outlined"
value={locale}
onChange={onLocaleSelected}
size="small"
select
>
<MenuItem key="de" value="de">
<img src={DEflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;DE
</MenuItem>
<MenuItem key="en" value="en">
<img src={GBflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;EN
</MenuItem>
<MenuItem key="fr" value="fr">
<img src={FRflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;FR
</MenuItem>
<MenuItem key="it" value="it">
<img src={ITflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;IT
</MenuItem>
<MenuItem key="nl" value="nl">
<img src={NLflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;NL
</MenuItem>
<MenuItem key="no" value="no">
<img src={NOflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;NO
</MenuItem>
<MenuItem key="pl" value="pl">
<img src={PLflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;PL
</MenuItem>
<MenuItem key="sk" value="sk">
<img src={SKflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;SK
</MenuItem>
<MenuItem key="sv" value="sv">
<img src={SVflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;SV
</MenuItem>
<MenuItem key="tr" value="tr">
<img src={TRflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;TR
</MenuItem>
</TextField>
</Box>
<Box>
<Button variant="outlined" fullWidth color="primary" onClick={() => signOut(true)}>
{LL.SIGN_OUT()}
</Button>
</Box>
</Box>
</Popover>
</> </>
); );
}; };

View File

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

View File

@@ -0,0 +1,171 @@
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import CastIcon from '@mui/icons-material/Cast';
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import ImportExportIcon from '@mui/icons-material/ImportExport';
import LockIcon from '@mui/icons-material/Lock';
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import TuneIcon from '@mui/icons-material/Tune';
import { List, ListItem, ListItemAvatar, ListItemText, Avatar, ListItemButton, ListItemIcon } from '@mui/material';
import { Link } from 'react-router-dom';
import type { FC } from 'react';
import { SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
const Settings: FC = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.SETTINGS(0));
return (
<SectionContent>
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
<ListItem
disablePadding
secondaryAction={
<ListItemIcon style={{ justifyContent: 'right', verticalAlign: 'middle' }}>
<NavigateNextIcon />
</ListItemIcon>
}
>
<ListItemButton component={Link} to="ems-esp">
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#134ba2', color: 'white' }}>
<TuneIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.APPLICATION_SETTINGS()} secondary="Modify EMS-ESP system settings" />
</ListItemButton>
</ListItem>
<ListItem
disablePadding
secondaryAction={
<ListItemIcon style={{ justifyContent: 'right', color: 'lightblue', verticalAlign: 'middle' }}>
<NavigateNextIcon />
</ListItemIcon>
}
>
<ListItemButton component={Link} to="network">
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#40828f', color: 'white' }}>
<SettingsEthernetIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.NETWORK(0)} secondary="Configure Network settings" />
</ListItemButton>
</ListItem>
<ListItem
disablePadding
secondaryAction={
<ListItemIcon style={{ justifyContent: 'right', color: 'lightblue', verticalAlign: 'middle' }}>
<NavigateNextIcon />
</ListItemIcon>
}
>
<ListItemButton component={Link} to="ap">
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#5f9a5f', color: 'white' }}>
<SettingsInputAntennaIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.ACCESS_POINT(0)} secondary="Configure Access Point" />
</ListItemButton>
</ListItem>
<ListItem
disablePadding
secondaryAction={
<ListItemIcon style={{ justifyContent: 'right', color: 'lightblue', verticalAlign: 'middle' }}>
<NavigateNextIcon />
</ListItemIcon>
}
>
<ListItemButton component={Link} to="ntp">
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#c5572c', color: 'white' }}>
<AccessTimeIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="NTP" secondary="Configure Network Time" />
</ListItemButton>
</ListItem>
<ListItem
disablePadding
secondaryAction={
<ListItemIcon style={{ justifyContent: 'right', color: 'lightblue', verticalAlign: 'middle' }}>
<NavigateNextIcon />
</ListItemIcon>
}
>
<ListItemButton component={Link} to="mqtt">
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#68374d', color: 'white' }}>
<DeviceHubIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="MQTT" secondary="Configure MQTT" />
</ListItemButton>
</ListItem>
<ListItem
disablePadding
secondaryAction={
<ListItemIcon style={{ justifyContent: 'right', color: 'lightblue', verticalAlign: 'middle' }}>
<NavigateNextIcon />
</ListItemIcon>
}
>
<ListItemButton component={Link} to="ota">
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#efc34b', color: 'white' }}>
<CastIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="OTA" secondary="Configure OTA" />
</ListItemButton>
</ListItem>
<ListItem
disablePadding
secondaryAction={
<ListItemIcon style={{ justifyContent: 'right', color: 'lightblue', verticalAlign: 'middle' }}>
<NavigateNextIcon />
</ListItemIcon>
}
>
<ListItemButton component={Link} to="security">
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#b1395f', color: 'white' }}>
<LockIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.SECURITY(0)} secondary="Configure user administration" />
</ListItemButton>
</ListItem>
<ListItem
disablePadding
secondaryAction={
<ListItemIcon style={{ justifyContent: 'right', color: 'lightblue', verticalAlign: 'middle' }}>
<NavigateNextIcon />
</ListItemIcon>
}
>
<ListItemButton component={Link} to="upload">
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#5d89f7', color: 'white' }}>
<ImportExportIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.UPLOAD_DOWNLOAD()} secondary="Upload/Download Settings and Firmware" />
</ListItemButton>
</ListItem>
</List>
</SectionContent>
);
};
export default Settings;

View File

@@ -205,7 +205,7 @@ const APSettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SETTINGS_OF(LL.ACCESS_POINT(1))} titleGutter> <SectionContent title={LL.SETTINGS_OF(LL.ACCESS_POINT(1))}>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()} {content()}
</SectionContent> </SectionContent>

View File

@@ -99,11 +99,7 @@ const APStatusForm: FC = () => {
); );
}; };
return ( return <SectionContent title={LL.STATUS_OF(LL.ACCESS_POINT(1))}>{content()}</SectionContent>;
<SectionContent title={LL.STATUS_OF(LL.ACCESS_POINT(1))} titleGutter>
{content()}
</SectionContent>
);
}; };
export default APStatusForm; export default APStatusForm;

View File

@@ -22,16 +22,11 @@ const AccessPoint: FC = () => {
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="/ap/status" label={LL.STATUS_OF(LL.ACCESS_POINT(1))} /> <Tab value="settings" label={LL.SETTINGS_OF(LL.ACCESS_POINT(1))} disabled={!authenticatedContext.me.admin} />
<Tab <Tab value="status" label={LL.STATUS_OF(LL.ACCESS_POINT(1))} />
value="/ap/settings"
label={LL.SETTINGS_OF(LL.ACCESS_POINT(1))}
disabled={!authenticatedContext.me.admin}
/>
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<APStatusForm />} /> <Route path="status" element={<APStatusForm />} />
<Route index element={<Navigate to="status" />} />
<Route <Route
path="settings" path="settings"
element={ element={
@@ -40,7 +35,7 @@ const AccessPoint: FC = () => {
</RequireAdmin> </RequireAdmin>
} }
/> />
<Route path="*" element={<Navigate replace to="/ap/status" />} /> <Route path="*" element={<Navigate replace to="settings" />} />
</Routes> </Routes>
</> </>
); );

View File

@@ -21,8 +21,8 @@ const Mqtt: FC = () => {
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="/mqtt/status" label={LL.STATUS_OF('MQTT')} /> <Tab value="settings" label={LL.SETTINGS_OF('MQTT')} disabled={!authenticatedContext.me.admin} />
<Tab value="/mqtt/settings" label={LL.SETTINGS_OF('MQTT')} disabled={!authenticatedContext.me.admin} /> <Tab value="status" label={LL.STATUS_OF('MQTT')} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<MqttStatusForm />} /> <Route path="status" element={<MqttStatusForm />} />
@@ -34,7 +34,7 @@ const Mqtt: FC = () => {
</RequireAdmin> </RequireAdmin>
} }
/> />
<Route path="*" element={<Navigate replace to="/mqtt/status" />} /> <Route path="*" element={<Navigate replace to="settings" />} />
</Routes> </Routes>
</> </>
); );

View File

@@ -449,7 +449,7 @@ const MqttSettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SETTINGS_OF('MQTT')} titleGutter> <SectionContent title={LL.SETTINGS_OF('MQTT')}>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()} {content()}
</SectionContent> </SectionContent>

View File

@@ -146,11 +146,7 @@ const MqttStatusForm: FC = () => {
); );
}; };
return ( return <SectionContent title={LL.STATUS_OF('MQTT')}>{content()}</SectionContent>;
<SectionContent title={LL.STATUS_OF('MQTT')} titleGutter>
{content()}
</SectionContent>
);
}; };
export default MqttStatusForm; export default MqttStatusForm;

View File

@@ -44,13 +44,9 @@ const NetworkConnection: FC = () => {
}} }}
> >
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="/network/status" label={LL.STATUS_OF(LL.NETWORK(1))} /> <Tab value="settings" label={LL.SETTINGS_OF(LL.NETWORK(1))} disabled={!authenticatedContext.me.admin} />
<Tab value="/network/scan" label={LL.NETWORK_SCAN()} disabled={!authenticatedContext.me.admin} /> <Tab value="scan" label={LL.NETWORK_SCAN()} disabled={!authenticatedContext.me.admin} />
<Tab <Tab value="status" label={LL.STATUS_OF(LL.NETWORK(1))} />
value="/network/settings"
label={LL.SETTINGS_OF(LL.NETWORK(1))}
disabled={!authenticatedContext.me.admin}
/>
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<NetworkStatusForm />} /> <Route path="status" element={<NetworkStatusForm />} />
@@ -70,7 +66,7 @@ const NetworkConnection: FC = () => {
</RequireAdmin> </RequireAdmin>
} }
/> />
<Route path="*" element={<Navigate replace to="/network/status" />} /> <Route path="*" element={<Navigate replace to="settings" />} />
</Routes> </Routes>
</WiFiConnectionContext.Provider> </WiFiConnectionContext.Provider>
); );

View File

@@ -360,7 +360,7 @@ const WiFiSettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SETTINGS_OF(LL.NETWORK(1))} titleGutter> <SectionContent title={LL.SETTINGS_OF(LL.NETWORK(1))}>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <RestartMonitor /> : content()} {restarting ? <RestartMonitor /> : content()}
</SectionContent> </SectionContent>

View File

@@ -193,11 +193,7 @@ const NetworkStatusForm: FC = () => {
); );
}; };
return ( return <SectionContent title={LL.STATUS_OF(LL.NETWORK(1))}>{content()}</SectionContent>;
<SectionContent title={LL.STATUS_OF(LL.NETWORK(1))} titleGutter>
{content()}
</SectionContent>
);
}; };
export default NetworkStatusForm; export default NetworkStatusForm;

View File

@@ -130,7 +130,7 @@ const NTPSettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SETTINGS_OF('NTP')} titleGutter> <SectionContent title={LL.SETTINGS_OF('NTP')}>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()} {content()}
</SectionContent> </SectionContent>

View File

@@ -216,11 +216,7 @@ const NTPStatusForm: FC = () => {
); );
}; };
return ( return <SectionContent title={LL.STATUS_OF('NTP')}>{content()}</SectionContent>;
<SectionContent title={LL.STATUS_OF('NTP')} titleGutter>
{content()}
</SectionContent>
);
}; };
export default NTPStatusForm; export default NTPStatusForm;

View File

@@ -20,8 +20,8 @@ const NetworkTime: FC = () => {
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="/ntp/status" label={LL.STATUS_OF('NTP')} /> <Tab value="settings" label={LL.SETTINGS_OF('NTP')} disabled={!authenticatedContext.me.admin} />
<Tab value="/ntp/settings" label={LL.SETTINGS_OF('NTP')} disabled={!authenticatedContext.me.admin} /> <Tab value="status" label={LL.STATUS_OF('NTP')} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<NTPStatusForm />} /> <Route path="status" element={<NTPStatusForm />} />
@@ -33,7 +33,7 @@ const NetworkTime: FC = () => {
</RequireAdmin> </RequireAdmin>
} }
/> />
<Route path="*" element={<Navigate replace to="/ntp/status" />} /> <Route path="*" element={<Navigate replace to="settings" />} />
</Routes> </Routes>
</> </>
); );

View File

@@ -14,7 +14,8 @@ import {
SectionContent, SectionContent,
ValidatedPasswordField, ValidatedPasswordField,
ValidatedTextField, ValidatedTextField,
BlockNavigation BlockNavigation,
useLayoutTitle
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
@@ -61,6 +62,8 @@ const OTASettingsForm: FC = () => {
} }
}; };
useLayoutTitle('OTA');
return ( return (
<> <>
<BlockFormControlLabel <BlockFormControlLabel
@@ -117,7 +120,7 @@ const OTASettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SETTINGS_OF('OTA')} titleGutter> <SectionContent title={LL.SETTINGS_OF('OTA')}>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()} {content()}
</SectionContent> </SectionContent>

View File

@@ -232,7 +232,7 @@ const ManageUsersForm: FC = () => {
}; };
return ( return (
<SectionContent title={LL.MANAGE_USERS()} titleGutter> <SectionContent title={LL.MANAGE_USERS()}>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()} {content()}
</SectionContent> </SectionContent>

View File

@@ -17,13 +17,13 @@ const Security: FC = () => {
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="/security/users" label={LL.MANAGE_USERS()} /> <Tab value="settings" label={LL.SETTINGS_OF(LL.SECURITY(1))} />
<Tab value="/security/settings" label={LL.SETTINGS_OF(LL.SECURITY(1))} /> <Tab value="users" label={LL.MANAGE_USERS()} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="users" element={<ManageUsersForm />} /> <Route path="users" element={<ManageUsersForm />} />
<Route path="settings" element={<SecuritySettingsForm />} /> <Route path="settings" element={<SecuritySettingsForm />} />
<Route path="*" element={<Navigate replace to="/security/users" />} /> <Route path="*" element={<Navigate replace to="settings" />} />
</Routes> </Routes>
</> </>
); );

View File

@@ -96,7 +96,7 @@ const SecuritySettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SETTINGS_OF(LL.SECURITY(1))} titleGutter> <SectionContent title={LL.SETTINGS_OF(LL.SECURITY(1))}>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()} {content()}
</SectionContent> </SectionContent>

View File

@@ -0,0 +1,36 @@
import { Tab } from '@mui/material';
import { Navigate, Routes, Route } from 'react-router-dom';
import SystemLog from './SystemLog';
import SystemStatusForm from './SystemStatusForm';
import type { FC } from 'react';
import { useRouterTab, RouterTabs, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import EMSStatus from 'project/EMSStatus';
const Status: FC = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.STATUS_OF(''));
const { routerTab } = useRouterTab();
return (
<>
<RouterTabs value={routerTab}>
<Tab value="status" label={LL.SYSTEM(1)} />
<Tab value="emsesp-status" label="EMS-ESP" />
<Tab value="log" label={LL.LOG_OF(LL.SYSTEM(2))} />
</RouterTabs>
<Routes>
<Route path="status" element={<SystemStatusForm />} />
<Route path="emsesp-status" element={<EMSStatus />} />
<Route path="log" element={<SystemLog />} />
<Route path="*" element={<Navigate replace to="status" />} />
</Routes>
</>
);
};
export default Status;

View File

@@ -1,56 +0,0 @@
import { Tab } from '@mui/material';
import { useContext } from 'react';
import { Navigate, Routes, Route } from 'react-router-dom';
import OTASettingsForm from './OTASettingsForm';
import SystemLog from './SystemLog';
import SystemStatusForm from './SystemStatusForm';
import UploadFileForm from './UploadFileForm';
import type { FC } from 'react';
import { useRouterTab, RouterTabs, useLayoutTitle, RequireAdmin } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
const System: FC = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.SYSTEM(0));
const { me } = useContext(AuthenticatedContext);
const { routerTab } = useRouterTab();
return (
<>
<RouterTabs value={routerTab}>
<Tab value="/system/status" label={LL.STATUS_OF(LL.SYSTEM(1))} />
<Tab value="/system/log" label={LL.LOG_OF(LL.SYSTEM(2))} />
<Tab value="/system/ota" label={LL.SETTINGS_OF('OTA')} disabled={!me.admin} />
<Tab value="/system/upload" label={LL.UPLOAD_DOWNLOAD()} disabled={!me.admin} />
</RouterTabs>
<Routes>
<Route path="status" element={<SystemStatusForm />} />
<Route path="log" element={<SystemLog />} />
<Route
path="ota"
element={
<RequireAdmin>
<OTASettingsForm />
</RequireAdmin>
}
/>
<Route
path="upload"
element={
<RequireAdmin>
<UploadFileForm />
</RequireAdmin>
}
/>
<Route path="*" element={<Navigate replace to="/system/status" />} />
</Routes>
</>
);
};
export default System;

View File

@@ -345,7 +345,7 @@ const SystemStatusForm: FC = () => {
}; };
return ( return (
<SectionContent title={LL.STATUS_OF(LL.SYSTEM(1))} titleGutter> <SectionContent title={LL.STATUS_OF(LL.SYSTEM(1))}>
{restarting ? <RestartMonitor /> : content()} {restarting ? <RestartMonitor /> : content()}
{data && ( {data && (
<SystemStatusVersionDialog <SystemStatusVersionDialog

View File

@@ -6,12 +6,12 @@ import { toast } from 'react-toastify';
import RestartMonitor from './RestartMonitor'; import RestartMonitor from './RestartMonitor';
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { SectionContent, SingleUpload } from 'components'; import { SectionContent, SingleUpload, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from 'project/api'; import * as EMSESP from 'project/api';
const UploadFileForm: FC = () => { const UploadDownload: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [restarting, setRestarting] = useState<boolean>(); const [restarting, setRestarting] = useState<boolean>();
const [md5, setMd5] = useState<string>(); const [md5, setMd5] = useState<string>();
@@ -127,6 +127,8 @@ const UploadFileForm: FC = () => {
}); });
}; };
useLayoutTitle(LL.UPLOAD_DOWNLOAD());
const content = () => ( const content = () => (
<> <>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary"> <Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
@@ -214,11 +216,7 @@ const UploadFileForm: FC = () => {
)} )}
</> </>
); );
return ( return <SectionContent title={LL.UPLOAD_DOWNLOAD()}>{restarting ? <RestartMonitor /> : content()}</SectionContent>;
<SectionContent title={LL.UPLOAD_DOWNLOAD()} titleGutter>
{restarting ? <RestartMonitor /> : content()}
</SectionContent>
);
}; };
export default UploadFileForm; export default UploadDownload;

View File

@@ -12,7 +12,6 @@ const de: Translation = {
USERNAME: 'Nutzername', USERNAME: 'Nutzername',
PASSWORD: 'Passwort', PASSWORD: 'Passwort',
SU_PASSWORD: 'su Passwort', SU_PASSWORD: 'su Passwort',
DASHBOARD: 'Kontrollzentrum',
SETTINGS_OF: '{0} Einstellungen', SETTINGS_OF: '{0} Einstellungen',
HELP_OF: '{0} Hilfe', HELP_OF: '{0} Hilfe',
LOGGED_IN: 'Eingeloggt als {name}', LOGGED_IN: 'Eingeloggt als {name}',
@@ -37,8 +36,6 @@ const de: Translation = {
BRAND: 'Marke', BRAND: 'Marke',
ENTITY_NAME: 'Entitätsname', ENTITY_NAME: 'Entitätsname',
VALUE: '{{Wert|wert}}', VALUE: '{{Wert|wert}}',
DEVICE_DATA: 'Gerätedaten',
SENSOR_DATA: 'Sensordaten',
DEVICES: 'Geräte', DEVICES: 'Geräte',
SENSORS: 'Sensoren', SENSORS: 'Sensoren',
RUN_COMMAND: 'Befehl ausführen', RUN_COMMAND: 'Befehl ausführen',
@@ -163,9 +160,7 @@ const de: Translation = {
OPTIONS: 'Optionen', OPTIONS: 'Optionen',
NAME: 'Name', NAME: 'Name',
CUSTOMIZATIONS_RESET: 'Möchten Sie wirklich alle Anpassungen entfernen, einschließlich der benutzerdefinierten Einstellungen der Temperatur- und Analogsensoren?', CUSTOMIZATIONS_RESET: 'Möchten Sie wirklich alle Anpassungen entfernen, einschließlich der benutzerdefinierten Einstellungen der Temperatur- und Analogsensoren?',
DEVICE_ENTITIES: 'Geräteentitäten',
SUPPORT_INFORMATION: 'Unterstützende Informationen', SUPPORT_INFORMATION: 'Unterstützende Informationen',
CLICK_HERE: 'Hier klicken',
HELP_INFORMATION_1: 'EMS-ESP Konfigurationsanweisungen und mehr finden Sie im Online-Wiki', HELP_INFORMATION_1: 'EMS-ESP Konfigurationsanweisungen und mehr finden Sie im Online-Wiki',
HELP_INFORMATION_2: 'Für einen Live-Community-Chat besuchen Sie unseren Discord-Server', HELP_INFORMATION_2: 'Für einen Live-Community-Chat besuchen Sie unseren Discord-Server',
HELP_INFORMATION_3: 'Um neue Funktionen anzufragen oder Fehler zu melden, eröffnen Sie ein Issue auf Github', HELP_INFORMATION_3: 'Um neue Funktionen anzufragen oder Fehler zu melden, eröffnen Sie ein Issue auf Github',

View File

@@ -12,7 +12,6 @@ const en: Translation = {
USERNAME: 'Username', USERNAME: 'Username',
PASSWORD: 'Password', PASSWORD: 'Password',
SU_PASSWORD: 'su Password', SU_PASSWORD: 'su Password',
DASHBOARD: 'Dashboard',
SETTINGS_OF: '{0} Settings', SETTINGS_OF: '{0} Settings',
HELP_OF: '{0} Help', HELP_OF: '{0} Help',
LOGGED_IN: 'Logged in as {name}', LOGGED_IN: 'Logged in as {name}',
@@ -37,8 +36,6 @@ const en: Translation = {
BRAND: 'Brand', BRAND: 'Brand',
ENTITY_NAME: 'Entity Name', ENTITY_NAME: 'Entity Name',
VALUE: '{{Value|value}}', VALUE: '{{Value|value}}',
DEVICE_DATA: 'Device Data',
SENSOR_DATA: 'Sensor Data',
DEVICES: 'Devices', DEVICES: 'Devices',
SENSORS: 'Sensors', SENSORS: 'Sensors',
RUN_COMMAND: 'Call Command', RUN_COMMAND: 'Call Command',
@@ -163,9 +160,7 @@ const en: Translation = {
OPTIONS: 'Options', OPTIONS: 'Options',
NAME: 'Name', NAME: 'Name',
CUSTOMIZATIONS_RESET: 'Are you sure you want remove all customizations including the custom settings of the Temperature and Analog sensors?', CUSTOMIZATIONS_RESET: 'Are you sure you want remove all customizations including the custom settings of the Temperature and Analog sensors?',
DEVICE_ENTITIES: 'Device Entities',
SUPPORT_INFORMATION: 'Support Information', SUPPORT_INFORMATION: 'Support Information',
CLICK_HERE: 'Click Here',
HELP_INFORMATION_1: 'Visit the online wiki to get instructions on how to configure EMS-ESP', HELP_INFORMATION_1: 'Visit the online wiki to get instructions on how to configure EMS-ESP',
HELP_INFORMATION_2: 'For live community chat join our Discord server', HELP_INFORMATION_2: 'For live community chat join our Discord server',
HELP_INFORMATION_3: 'To request a feature or report a bug', HELP_INFORMATION_3: 'To request a feature or report a bug',

View File

@@ -12,7 +12,6 @@ const fr: Translation = {
USERNAME: 'Nom d\'utilisateur', USERNAME: 'Nom d\'utilisateur',
PASSWORD: 'Mot de passe', PASSWORD: 'Mot de passe',
SU_PASSWORD: 'Mot de passe su', SU_PASSWORD: 'Mot de passe su',
DASHBOARD: 'Tableau de bord',
SETTINGS_OF: 'Paramètres {0}', SETTINGS_OF: 'Paramètres {0}',
HELP_OF: 'Aide {0}', HELP_OF: 'Aide {0}',
LOGGED_IN: 'Connecté en tant que {name}', LOGGED_IN: 'Connecté en tant que {name}',
@@ -37,8 +36,6 @@ const fr: Translation = {
BRAND: 'Marque', BRAND: 'Marque',
ENTITY_NAME: 'Nom de l\'entité', ENTITY_NAME: 'Nom de l\'entité',
VALUE: 'Valeur', VALUE: 'Valeur',
DEVICE_DATA: 'Données des appareils',
SENSOR_DATA: 'Données des capteurs',
DEVICES: 'Appareils', DEVICES: 'Appareils',
SENSORS: 'Capteurs', SENSORS: 'Capteurs',
RUN_COMMAND: 'Lancer une commande', RUN_COMMAND: 'Lancer une commande',
@@ -163,9 +160,7 @@ const fr: Translation = {
OPTIONS: 'Options', OPTIONS: 'Options',
NAME: 'Nom', NAME: 'Nom',
CUSTOMIZATIONS_RESET: 'Êtes-vous sûr de vouloir supprimer toutes les personnalisations, y compris les paramètres personnalisés des capteurs de température et analogiques ?', CUSTOMIZATIONS_RESET: 'Êtes-vous sûr de vouloir supprimer toutes les personnalisations, y compris les paramètres personnalisés des capteurs de température et analogiques ?',
DEVICE_ENTITIES: 'Entités de l\'appareil',
SUPPORT_INFORMATION: 'Information de support', SUPPORT_INFORMATION: 'Information de support',
CLICK_HERE: 'Cliquez ici',
HELP_INFORMATION_1: 'Visitez le wiki en ligne pour obtenir des instructions sur la façon de configurer EMS-ESP.', HELP_INFORMATION_1: 'Visitez le wiki en ligne pour obtenir des instructions sur la façon de configurer EMS-ESP.',
HELP_INFORMATION_2: 'Pour une discussion en direct avec la communauté, rejoignez notre serveur Discord', HELP_INFORMATION_2: 'Pour une discussion en direct avec la communauté, rejoignez notre serveur Discord',
HELP_INFORMATION_3: 'Pour demander une fonctionnalité ou signaler un problème', HELP_INFORMATION_3: 'Pour demander une fonctionnalité ou signaler un problème',

View File

@@ -12,7 +12,6 @@ const it: Translation = {
USERNAME: 'Nome Utente', USERNAME: 'Nome Utente',
PASSWORD: 'Password', PASSWORD: 'Password',
SU_PASSWORD: 'su Password', SU_PASSWORD: 'su Password',
DASHBOARD: 'Pannello di Controllo',
SETTINGS_OF: 'Impostazioni {0}', SETTINGS_OF: 'Impostazioni {0}',
HELP_OF: '{0} Aiuto', HELP_OF: '{0} Aiuto',
LOGGED_IN: 'Registrato come {name}', LOGGED_IN: 'Registrato come {name}',
@@ -37,8 +36,6 @@ const it: Translation = {
BRAND: 'Marca', BRAND: 'Marca',
ENTITY_NAME: 'Nome Entità', ENTITY_NAME: 'Nome Entità',
VALUE: '{{Valore|valore}}', VALUE: '{{Valore|valore}}',
DEVICE_DATA: 'Device Data',
SENSOR_DATA: 'Sensor Data',
DEVICES: 'Dispositivi', DEVICES: 'Dispositivi',
SENSORS: 'Sensori', SENSORS: 'Sensori',
RUN_COMMAND: 'Esegui', RUN_COMMAND: 'Esegui',
@@ -165,9 +162,7 @@ const it: Translation = {
OPTIONS: 'Opzioni', OPTIONS: 'Opzioni',
NAME: 'Nome', NAME: 'Nome',
CUSTOMIZATIONS_RESET: 'Sei sicuro di voler rimuovere tutte le personalizzazioni incluse le impostazioni personalizzate dei sensori di temperatura e analogici?', CUSTOMIZATIONS_RESET: 'Sei sicuro di voler rimuovere tutte le personalizzazioni incluse le impostazioni personalizzate dei sensori di temperatura e analogici?',
DEVICE_ENTITIES: 'Entità Dispositivo',
SUPPORT_INFORMATION: 'Informazioni di Supporto', SUPPORT_INFORMATION: 'Informazioni di Supporto',
CLICK_HERE: 'Clicca qui',
HELP_INFORMATION_1: 'Visita il wiki online per ottenere istruzioni su come configurare EMS-ESP', HELP_INFORMATION_1: 'Visita il wiki online per ottenere istruzioni su come configurare EMS-ESP',
HELP_INFORMATION_2: 'Per la chat della community dal vivo unisciti al nostro server Discord', HELP_INFORMATION_2: 'Per la chat della community dal vivo unisciti al nostro server Discord',
HELP_INFORMATION_3: 'Per richiedere una funzionalità o segnalare un errore', HELP_INFORMATION_3: 'Per richiedere una funzionalità o segnalare un errore',

View File

@@ -12,7 +12,6 @@ const nl: Translation = {
USERNAME: 'Gebruikersnaam', USERNAME: 'Gebruikersnaam',
PASSWORD: 'Wachtwoord', PASSWORD: 'Wachtwoord',
SU_PASSWORD: 'su Wachtwoord', SU_PASSWORD: 'su Wachtwoord',
DASHBOARD: 'Dashboard',
SETTINGS_OF: '{0} Instellingen', SETTINGS_OF: '{0} Instellingen',
HELP_OF: '{0} Help', HELP_OF: '{0} Help',
LOGGED_IN: 'Ingelogd als {name}', LOGGED_IN: 'Ingelogd als {name}',
@@ -37,8 +36,6 @@ const nl: Translation = {
BRAND: 'Merk', BRAND: 'Merk',
ENTITY_NAME: 'Entiteit', ENTITY_NAME: 'Entiteit',
VALUE: '{{Waarde|waarde}}', VALUE: '{{Waarde|waarde}}',
SENSOR_DATA: 'Sensor data',
DEVICE_DATA: 'Apparaat data',
DEVICES: 'Apparaten', DEVICES: 'Apparaten',
SENSORS: 'Sensoren', SENSORS: 'Sensoren',
RUN_COMMAND: 'Call commando', RUN_COMMAND: 'Call commando',
@@ -163,9 +160,7 @@ const nl: Translation = {
OPTIONS: 'Opties', OPTIONS: 'Opties',
NAME: 'Naam', NAME: 'Naam',
CUSTOMIZATIONS_RESET: 'Weet je zeker dat je alle custom aanpassingen wilt verwijderen inclusief de custom instellingen voor analoge temperatuursensoren?', CUSTOMIZATIONS_RESET: 'Weet je zeker dat je alle custom aanpassingen wilt verwijderen inclusief de custom instellingen voor analoge temperatuursensoren?',
DEVICE_ENTITIES: 'Apparaat Entiteiten',
SUPPORT_INFORMATION: 'Support Informatie', SUPPORT_INFORMATION: 'Support Informatie',
CLICK_HERE: 'Klik Hier',
HELP_INFORMATION_1: 'Bezoek de online wiki om instructies te vinden om EMS-ESP te configureren', HELP_INFORMATION_1: 'Bezoek de online wiki om instructies te vinden om EMS-ESP te configureren',
HELP_INFORMATION_2: 'Voor de live community ga naar de Discord server', HELP_INFORMATION_2: 'Voor de live community ga naar de Discord server',
HELP_INFORMATION_3: 'Om een nieuwe feature te vragen of een bug te rapporteren', HELP_INFORMATION_3: 'Om een nieuwe feature te vragen of een bug te rapporteren',

View File

@@ -12,7 +12,6 @@ const no: Translation = {
USERNAME: 'Brukernavn', USERNAME: 'Brukernavn',
PASSWORD: 'Passord', PASSWORD: 'Passord',
SU_PASSWORD: 'su Passord', SU_PASSWORD: 'su Passord',
DASHBOARD: 'Dashboard',
SETTINGS_OF: '{0} Innstillinger', SETTINGS_OF: '{0} Innstillinger',
HELP_OF: '{0} Hjelp', HELP_OF: '{0} Hjelp',
LOGGED_IN: 'Logget in som {name}', LOGGED_IN: 'Logget in som {name}',
@@ -37,8 +36,6 @@ const no: Translation = {
BRAND: 'Fabrikat', BRAND: 'Fabrikat',
ENTITY_NAME: 'Objektsnavn', ENTITY_NAME: 'Objektsnavn',
VALUE: '{{Verdi|verdi}}', VALUE: '{{Verdi|verdi}}',
DEVICE_DATA: 'Enheterdata',
SENSOR_DATA: 'Sensordata',
DEVICES: 'Enheter', DEVICES: 'Enheter',
SENSORS: 'Sensorer', SENSORS: 'Sensorer',
RUN_COMMAND: 'Kjør kommando', RUN_COMMAND: 'Kjør kommando',
@@ -163,9 +160,7 @@ const no: Translation = {
OPTIONS: 'Alternativ', OPTIONS: 'Alternativ',
NAME: 'Navn', NAME: 'Navn',
CUSTOMIZATIONS_RESET: 'Er du sikker på att du vil fjerne tilpassninger inkludert innstillinger for Temperatur og Analoge sensorer?', CUSTOMIZATIONS_RESET: 'Er du sikker på att du vil fjerne tilpassninger inkludert innstillinger for Temperatur og Analoge sensorer?',
DEVICE_ENTITIES: 'Enhets objekter',
SUPPORT_INFORMATION: 'Supportinformasjon', SUPPORT_INFORMATION: 'Supportinformasjon',
CLICK_HERE: 'Klikk her',
HELP_INFORMATION_1: 'Besøk wiki for instruksjoner for å konfigurere EMS-ESP', HELP_INFORMATION_1: 'Besøk wiki for instruksjoner for å konfigurere EMS-ESP',
HELP_INFORMATION_2: 'For community-support besøk vår Discord-server', HELP_INFORMATION_2: 'For community-support besøk vår Discord-server',
HELP_INFORMATION_3: 'For å be om en ny funksjon eller melde feil', HELP_INFORMATION_3: 'For å be om en ny funksjon eller melde feil',

View File

@@ -12,7 +12,6 @@ const pl: BaseTranslation = {
USERNAME: '{{Użytkownik|Nazwa użytkownika|}}', USERNAME: '{{Użytkownik|Nazwa użytkownika|}}',
PASSWORD: 'Hasło', PASSWORD: 'Hasło',
SU_PASSWORD: 'Hasło "su"', SU_PASSWORD: 'Hasło "su"',
DASHBOARD: 'Pulpit',
SETTINGS_OF: 'Ustawienia {0}', SETTINGS_OF: 'Ustawienia {0}',
HELP_OF: 'Pomoc {0}', HELP_OF: 'Pomoc {0}',
LOGGED_IN: 'Zalogowano użytkownika {name}.', LOGGED_IN: 'Zalogowano użytkownika {name}.',
@@ -37,8 +36,6 @@ const pl: BaseTranslation = {
VERSION: 'Wersja', VERSION: 'Wersja',
ENTITY_NAME: '{{N|n|}}azwa encji', ENTITY_NAME: '{{N|n|}}azwa encji',
VALUE: '{{W|w|}}artość', VALUE: '{{W|w|}}artość',
DEVICE_DATA: 'Dane z urządzeń',
SENSOR_DATA: 'Dane z czujników',
DEVICES: 'Urządzenia', DEVICES: 'Urządzenia',
SENSORS: 'Czujniki', SENSORS: 'Czujniki',
RUN_COMMAND: 'Wykonaj komendę', RUN_COMMAND: 'Wykonaj komendę',
@@ -163,9 +160,7 @@ const pl: BaseTranslation = {
OPTIONS: 'Opcje', OPTIONS: 'Opcje',
NAME: '{{Nazwa|nazwa|}}', NAME: '{{Nazwa|nazwa|}}',
CUSTOMIZATIONS_RESET: 'Na pewno chcesz usunąć wszystkie personalizacje łącznie z ustawieniami dla czujników temperatury 1-Wire® i urządzeń podłączonych do EMS-ESP?', CUSTOMIZATIONS_RESET: 'Na pewno chcesz usunąć wszystkie personalizacje łącznie z ustawieniami dla czujników temperatury 1-Wire® i urządzeń podłączonych do EMS-ESP?',
DEVICE_ENTITIES: 'Encje urządzenia',
SUPPORT_INFORMATION: '{{I|i|}}nformacj{{e|i|}} o systemie', SUPPORT_INFORMATION: '{{I|i|}}nformacj{{e|i|}} o systemie',
CLICK_HERE: 'Kliknij tu',
HELP_INFORMATION_1: 'Aby uzyskać instrukcje dotyczące konfiguracji EMS-ESP, skorzystaj z wiki w internecie', HELP_INFORMATION_1: 'Aby uzyskać instrukcje dotyczące konfiguracji EMS-ESP, skorzystaj z wiki w internecie',
HELP_INFORMATION_2: 'Aby dołączyć do naszego serwera Discord i komunikować się na żywo ze społecznością', HELP_INFORMATION_2: 'Aby dołączyć do naszego serwera Discord i komunikować się na żywo ze społecznością',
HELP_INFORMATION_3: 'Aby zaproponować nową funkcjonalność lub zgłosić problem', HELP_INFORMATION_3: 'Aby zaproponować nową funkcjonalność lub zgłosić problem',

View File

@@ -12,7 +12,6 @@ const sk: Translation = {
USERNAME: 'Užívateľské meno', USERNAME: 'Užívateľské meno',
PASSWORD: 'Heslo', PASSWORD: 'Heslo',
SU_PASSWORD: 'su heslo', SU_PASSWORD: 'su heslo',
DASHBOARD: 'Panel',
SETTINGS_OF: '{0} Nastavenia', SETTINGS_OF: '{0} Nastavenia',
HELP_OF: '{0} Pomoc', HELP_OF: '{0} Pomoc',
LOGGED_IN: 'Prihlásený ako {name}', LOGGED_IN: 'Prihlásený ako {name}',
@@ -37,8 +36,6 @@ const sk: Translation = {
BRAND: 'Značka', BRAND: 'Značka',
ENTITY_NAME: 'Názov entity', ENTITY_NAME: 'Názov entity',
VALUE: '{{Value|value}}', VALUE: '{{Value|value}}',
DEVICE_DATA: 'Dáta zariadenia',
SENSOR_DATA: 'Dáta snímača',
DEVICES: 'Zariadenia', DEVICES: 'Zariadenia',
SENSORS: 'Snímače', SENSORS: 'Snímače',
RUN_COMMAND: 'Volať príkaz', RUN_COMMAND: 'Volať príkaz',
@@ -163,9 +160,7 @@ const sk: Translation = {
OPTIONS: 'Možnosti', OPTIONS: 'Možnosti',
NAME: 'Názov', NAME: 'Názov',
CUSTOMIZATIONS_RESET: 'Naozaj chcete odstrániť všetky prispôsobenia vrátane vlastných nastavení snímačov teploty a analógových snímačov?', CUSTOMIZATIONS_RESET: 'Naozaj chcete odstrániť všetky prispôsobenia vrátane vlastných nastavení snímačov teploty a analógových snímačov?',
DEVICE_ENTITIES: 'Entity zariadenia',
SUPPORT_INFORMATION: 'Informácie o podpore', SUPPORT_INFORMATION: 'Informácie o podpore',
CLICK_HERE: 'Kliknite tu',
HELP_INFORMATION_1: 'Navštívte online wiki, kde nájdete pokyny na konfiguráciu EMS-ESP', HELP_INFORMATION_1: 'Navštívte online wiki, kde nájdete pokyny na konfiguráciu EMS-ESP',
HELP_INFORMATION_2: 'Pre živý komunitný chat sa pripojte na náš Discord server', HELP_INFORMATION_2: 'Pre živý komunitný chat sa pripojte na náš Discord server',
HELP_INFORMATION_3: 'Ak chcete požiadať o funkciu alebo nahlásiť chybu', HELP_INFORMATION_3: 'Ak chcete požiadať o funkciu alebo nahlásiť chybu',

View File

@@ -12,7 +12,6 @@ const sv: Translation = {
USERNAME: 'Användarnamn', USERNAME: 'Användarnamn',
PASSWORD: 'Lösenord', PASSWORD: 'Lösenord',
SU_PASSWORD: 'su Lösenord', SU_PASSWORD: 'su Lösenord',
DASHBOARD: 'Kontrollpanel',
SETTINGS_OF: '{0} Inställningar', SETTINGS_OF: '{0} Inställningar',
HELP_OF: '{0} Hjälp', HELP_OF: '{0} Hjälp',
LOGGED_IN: 'Inloggad som {name}', LOGGED_IN: 'Inloggad som {name}',
@@ -37,8 +36,6 @@ const sv: Translation = {
BRAND: 'Fabrikat', BRAND: 'Fabrikat',
ENTITY_NAME: 'Entitetsnamn', ENTITY_NAME: 'Entitetsnamn',
VALUE: '{{Värde|värde}}', VALUE: '{{Värde|värde}}',
DEVICE_DATA: 'Enhets data',
SENSOR_DATA: 'Sensor data',
DEVICES: 'Enheter', DEVICES: 'Enheter',
SENSORS: 'Sensorer', SENSORS: 'Sensorer',
RUN_COMMAND: 'Kör Kommando', RUN_COMMAND: 'Kör Kommando',
@@ -163,9 +160,7 @@ const sv: Translation = {
OPTIONS: 'Alternativ', OPTIONS: 'Alternativ',
NAME: 'Namn', 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 Temperatur och Analoga sensorer?',
DEVICE_ENTITIES: 'Enhets-entiteter',
SUPPORT_INFORMATION: 'Supportinformation', SUPPORT_INFORMATION: 'Supportinformation',
CLICK_HERE: 'Klicka Här',
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 för hur du kan konfigurera EMS-ESP',
HELP_INFORMATION_2: 'För community-support besök vår Discord-server', 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_3: 'Önska en ny funktion eller rapportera en bugg',

View File

@@ -12,7 +12,6 @@ const tr: Translation = {
USERNAME: 'Kullanıcı Adı', USERNAME: 'Kullanıcı Adı',
PASSWORD: 'Şifre', PASSWORD: 'Şifre',
SU_PASSWORD: 'SK Şifresi', SU_PASSWORD: 'SK Şifresi',
DASHBOARD: 'Gösterge Paneli',
SETTINGS_OF: '{0} Ayarlar', SETTINGS_OF: '{0} Ayarlar',
HELP_OF: '{0} Yardım', HELP_OF: '{0} Yardım',
LOGGED_IN: '{name} olarak giriş yapıldı', LOGGED_IN: '{name} olarak giriş yapıldı',
@@ -37,8 +36,6 @@ const tr: Translation = {
BRAND: 'Marka', BRAND: 'Marka',
ENTITY_NAME: 'Valık Adı', ENTITY_NAME: 'Valık Adı',
VALUE: '{{Değer|değer}}', VALUE: '{{Değer|değer}}',
DEVICE_DATA: 'Cihaz Bilgisi',
SENSOR_DATA: 'Sensör Bilgisi',
DEVICES: 'Cihazlar', DEVICES: 'Cihazlar',
SENSORS: 'Sensörler', SENSORS: 'Sensörler',
RUN_COMMAND: 'Çalıştırma Komutu', RUN_COMMAND: 'Çalıştırma Komutu',
@@ -163,9 +160,7 @@ const tr: Translation = {
OPTIONS: 'Seçenekler', OPTIONS: 'Seçenekler',
NAME: 'İsim', NAME: 'İsim',
CUSTOMIZATIONS_RESET: 'Sıcaklık ve Analog Sensörlerin özelleştirilmiş seçenekleri dahil bütün özelleştirmeleri kaldırmak istediğinizden emin misiniz?', CUSTOMIZATIONS_RESET: 'Sıcaklık ve Analog Sensörlerin özelleştirilmiş seçenekleri dahil bütün özelleştirmeleri kaldırmak istediğinizden emin misiniz?',
DEVICE_ENTITIES: 'Cihaz Varlıkları',
SUPPORT_INFORMATION: 'Destek Bilgileri', SUPPORT_INFORMATION: 'Destek Bilgileri',
CLICK_HERE: 'Buraya Tıklayın',
HELP_INFORMATION_1: 'EMS-ESPnin nasıl ayarlanacağı ile ilgili bilgileri edinmek için çevrimiçi WIKI sayfasını ziyaret edin', HELP_INFORMATION_1: 'EMS-ESPnin nasıl ayarlanacağı ile ilgili bilgileri edinmek için çevrimiçi WIKI sayfasını ziyaret edin',
HELP_INFORMATION_2: 'Canlı topluluk sohbeti için Discord sunucumuza katılın', HELP_INFORMATION_2: 'Canlı topluluk sohbeti için Discord sunucumuza katılın',
HELP_INFORMATION_3: 'Yeni bir özellik talep etmek yada hata bildirmek için', HELP_INFORMATION_3: 'Yeni bir özellik talep etmek yada hata bildirmek için',

View File

@@ -20,7 +20,8 @@ import {
ValidatedTextField, ValidatedTextField,
ButtonRow, ButtonRow,
MessageBox, MessageBox,
BlockNavigation BlockNavigation,
useLayoutTitle
} from 'components'; } from 'components';
import RestartMonitor from 'framework/system/RestartMonitor'; import RestartMonitor from 'framework/system/RestartMonitor';
@@ -36,7 +37,7 @@ export function boardProfileSelectItems() {
)); ));
} }
const SettingsApplication: FC = () => { const ApplicationSettings: FC = () => {
const { const {
loadData, loadData,
saveData, saveData,
@@ -97,6 +98,8 @@ const SettingsApplication: FC = () => {
}); });
}; };
useLayoutTitle(LL.APPLICATION_SETTINGS());
const content = () => { const content = () => {
if (!data) { if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />; return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
@@ -680,11 +683,11 @@ const SettingsApplication: FC = () => {
}; };
return ( return (
<SectionContent title={LL.APPLICATION_SETTINGS()} titleGutter> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <RestartMonitor /> : content()} {restarting ? <RestartMonitor /> : content()}
</SectionContent> </SectionContent>
); );
}; };
export default SettingsApplication; export default ApplicationSettings;

View File

@@ -13,17 +13,17 @@ import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import SettingsCustomEntitiesDialog from './SettingsCustomEntitiesDialog'; import SettingsCustomEntitiesDialog from './CustomEntitiesDialog';
import * as EMSESP from './api'; import * as EMSESP from './api';
import { DeviceValueTypeNames, DeviceValueUOM_s } from './types'; import { DeviceValueTypeNames, DeviceValueUOM_s } from './types';
import { entityItemValidation } from './validators'; import { entityItemValidation } from './validators';
import type { EntityItem } from './types'; import type { EntityItem } from './types';
import type { FC } from 'react'; import type { FC } from 'react';
import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components'; import { ButtonRow, FormLoader, SectionContent, BlockNavigation, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
const SettingsCustomEntities: FC = () => { const CustomEntities: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0); const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0); const blocker = useBlocker(numChanges !== 0);
@@ -31,6 +31,8 @@ const SettingsCustomEntities: FC = () => {
const [creating, setCreating] = useState<boolean>(false); const [creating, setCreating] = useState<boolean>(false);
const [dialogOpen, setDialogOpen] = useState<boolean>(false); const [dialogOpen, setDialogOpen] = useState<boolean>(false);
useLayoutTitle(LL.CUSTOM_ENTITIES(0));
const { const {
data: entities, data: entities,
send: fetchEntities, send: fetchEntities,
@@ -246,7 +248,7 @@ const SettingsCustomEntities: FC = () => {
}; };
return ( return (
<SectionContent title={LL.CUSTOM_ENTITIES(0)} titleGutter> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
<Box mb={2} color="warning.main"> <Box mb={2} color="warning.main">
<Typography variant="body2">{LL.ENTITIES_HELP_1()}</Typography> <Typography variant="body2">{LL.ENTITIES_HELP_1()}</Typography>
@@ -298,4 +300,4 @@ const SettingsCustomEntities: FC = () => {
); );
}; };
export default SettingsCustomEntities; export default CustomEntities;

View File

@@ -30,7 +30,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils'; import { numberValue, updateValue } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
type SettingsCustomEntitiesDialogProps = { type CustomEntitiesDialogProps = {
open: boolean; open: boolean;
creating: boolean; creating: boolean;
onClose: () => void; onClose: () => void;
@@ -39,14 +39,14 @@ type SettingsCustomEntitiesDialogProps = {
validator: Schema; validator: Schema;
}; };
const SettingsCustomEntitiesDialog = ({ const CustomEntitiesDialog = ({
open, open,
creating, creating,
onClose, onClose,
onSave, onSave,
selectedItem, selectedItem,
validator validator
}: SettingsCustomEntitiesDialogProps) => { }: CustomEntitiesDialogProps) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<EntityItem>(selectedItem); const [editItem, setEditItem] = useState<EntityItem>(selectedItem);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -281,4 +281,4 @@ const SettingsCustomEntitiesDialog = ({
); );
}; };
export default SettingsCustomEntitiesDialog; export default CustomEntitiesDialog;

View File

@@ -26,9 +26,9 @@ import { useState, useEffect, useCallback } from 'react';
import { useBlocker, useLocation } from 'react-router-dom'; import { useBlocker, useLocation } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import SettingsCustomizationDialog from './CustomizationDialog';
import EntityMaskToggle from './EntityMaskToggle'; import EntityMaskToggle from './EntityMaskToggle';
import OptionIcon from './OptionIcon'; import OptionIcon from './OptionIcon';
import SettingsCustomizationDialog from './SettingsCustomizationDialog';
import * as EMSESP from './api'; import * as EMSESP from './api';
@@ -37,14 +37,14 @@ import type { DeviceShort, DeviceEntity } from './types';
import type { FC } from 'react'; import type { FC } from 'react';
import { dialogStyle } from 'CustomTheme'; import { dialogStyle } from 'CustomTheme';
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { ButtonRow, SectionContent, MessageBox, BlockNavigation } from 'components'; import { ButtonRow, SectionContent, MessageBox, BlockNavigation, useLayoutTitle } from 'components';
import RestartMonitor from 'framework/system/RestartMonitor'; import RestartMonitor from 'framework/system/RestartMonitor';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
export const APIURL = window.location.origin + '/api/'; export const APIURL = window.location.origin + '/api/';
const SettingsCustomization: FC = () => { const Customization: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0); const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0); const blocker = useBlocker(numChanges !== 0);
@@ -58,6 +58,8 @@ const SettingsCustomization: FC = () => {
const [selectedDeviceEntity, setSelectedDeviceEntity] = useState<DeviceEntity>(); const [selectedDeviceEntity, setSelectedDeviceEntity] = useState<DeviceEntity>();
const [dialogOpen, setDialogOpen] = useState<boolean>(false); const [dialogOpen, setDialogOpen] = useState<boolean>(false);
useLayoutTitle(LL.CUSTOMIZATIONS());
// fetch devices first // fetch devices first
const { data: devices } = useRequest(EMSESP.readDevices); const { data: devices } = useRequest(EMSESP.readDevices);
@@ -508,9 +510,6 @@ const SettingsCustomization: FC = () => {
const renderContent = () => ( const renderContent = () => (
<> <>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.DEVICE_ENTITIES()}
</Typography>
{devices && renderDeviceList()} {devices && renderDeviceList()}
{deviceEntities && renderDeviceData()} {deviceEntities && renderDeviceData()}
{restartNeeded && ( {restartNeeded && (
@@ -561,7 +560,7 @@ const SettingsCustomization: FC = () => {
); );
return ( return (
<SectionContent title={LL.CUSTOMIZATIONS()} titleGutter> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <RestartMonitor /> : renderContent()} {restarting ? <RestartMonitor /> : renderContent()}
{selectedDeviceEntity && ( {selectedDeviceEntity && (
@@ -576,4 +575,4 @@ const SettingsCustomization: FC = () => {
); );
}; };
export default SettingsCustomization; export default Customization;

View File

@@ -31,7 +31,7 @@ type SettingsCustomizationDialogProps = {
selectedItem: DeviceEntity; selectedItem: DeviceEntity;
}; };
const SettingsCustomizationDialog = ({ open, onClose, onSave, selectedItem }: SettingsCustomizationDialogProps) => { const CustomizationDialog = ({ open, onClose, onSave, selectedItem }: SettingsCustomizationDialogProps) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<DeviceEntity>(selectedItem); const [editItem, setEditItem] = useState<DeviceEntity>(selectedItem);
const [error, setError] = useState<boolean>(false); const [error, setError] = useState<boolean>(false);
@@ -152,4 +152,4 @@ const SettingsCustomizationDialog = ({ open, onClose, onSave, selectedItem }: Se
); );
}; };
export default SettingsCustomizationDialog; export default CustomizationDialog;

View File

@@ -1,37 +0,0 @@
import { Tab } from '@mui/material';
import { Navigate, Route, Routes } from 'react-router-dom';
import DashboardDevices from './DashboardDevices';
import DashboardSensors from './DashboardSensors';
import DashboardStatus from './DashboardStatus';
import type { FC } from 'react';
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
const Dashboard: FC = () => {
const { routerTab } = useRouterTab();
const { LL } = useI18nContext();
useLayoutTitle(LL.DASHBOARD());
return (
<>
<RouterTabs value={routerTab}>
<Tab value="/dashboard/devices" label={LL.DEVICES()} />
<Tab value="/dashboard/sensors" label={LL.SENSORS()} />
<Tab value="/dashboard/status" label="Status" />
</RouterTabs>
<Routes>
<Route path="devices" element={<DashboardDevices />} />
<Route path="sensors" element={<DashboardSensors />} />
<Route path="status" element={<DashboardStatus />} />
<Route path="*" element={<Navigate replace to="/dashboard/devices" />} />
</Routes>
</>
);
};
export default Dashboard;

View File

@@ -38,8 +38,8 @@ import { useState, useContext, useEffect, useCallback, useLayoutEffect } from 'r
import { IconContext } from 'react-icons'; import { IconContext } from 'react-icons';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import DashboardDevicesDialog from './DashboardDevicesDialog';
import DeviceIcon from './DeviceIcon'; import DeviceIcon from './DeviceIcon';
import DashboardDevicesDialog from './DevicesDialog';
import * as EMSESP from './api'; import * as EMSESP from './api';
import { formatValue } from './deviceValue'; import { formatValue } from './deviceValue';
@@ -49,12 +49,12 @@ import { deviceValueItemValidation } from './validators';
import type { Device, DeviceValue } from './types'; import type { Device, DeviceValue } from './types';
import type { FC } from 'react'; import type { FC } from 'react';
import { dialogStyle } from 'CustomTheme'; import { dialogStyle } from 'CustomTheme';
import { ButtonRow, SectionContent, MessageBox } from 'components'; import { ButtonRow, SectionContent, MessageBox, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
const DashboardDevices: FC = () => { const Devices: FC = () => {
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [size, setSize] = useState([0, 0]); const [size, setSize] = useState([0, 0]);
@@ -66,6 +66,8 @@ const DashboardDevices: FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
useLayoutTitle(LL.DEVICES());
const { data: coreData, send: readCoreData } = useRequest(() => EMSESP.readCoreData(), { const { data: coreData, send: readCoreData } = useRequest(() => EMSESP.readCoreData(), {
initialData: { initialData: {
connected: true, connected: true,
@@ -608,7 +610,7 @@ const DashboardDevices: FC = () => {
}; };
return ( return (
<SectionContent title={LL.DEVICE_DATA()} titleGutter id="devices-window"> <SectionContent id="devices-window">
{renderCoreData()} {renderCoreData()}
{renderDeviceData()} {renderDeviceData()}
{renderDeviceDetails()} {renderDeviceDetails()}
@@ -636,4 +638,4 @@ const DashboardDevices: FC = () => {
); );
}; };
export default DashboardDevices; export default Devices;

View File

@@ -40,7 +40,7 @@ type DashboardDevicesDialogProps = {
progress: boolean; progress: boolean;
}; };
const DashboardDevicesDialog = ({ const DevicesDialog = ({
open, open,
onClose, onClose,
onSave, onSave,
@@ -204,4 +204,4 @@ const DashboardDevicesDialog = ({
); );
}; };
export default DashboardDevicesDialog; export default DevicesDialog;

View File

@@ -64,7 +64,7 @@ const showQuality = (stat: Stat) => {
} }
}; };
const DashboardStatus: FC = () => { const EMSStatus: FC = () => {
const { data: data, send: loadData, error } = useRequest(EMSESP.readStatus); const { data: data, send: loadData, error } = useRequest(EMSESP.readStatus);
const { LL } = useI18nContext(); const { LL } = useI18nContext();
@@ -272,11 +272,7 @@ const DashboardStatus: FC = () => {
); );
}; };
return ( return <SectionContent title={LL.EMS_BUS_STATUS_TITLE()}>{content()}</SectionContent>;
<SectionContent title={LL.EMS_BUS_STATUS_TITLE()} titleGutter>
{content()}
</SectionContent>
);
}; };
export default DashboardStatus; export default EMSStatus;

View File

@@ -1,9 +1,19 @@
import CommentIcon from '@mui/icons-material/CommentTwoTone'; import CommentIcon from '@mui/icons-material/CommentTwoTone';
import EastIcon from '@mui/icons-material/East';
import DownloadIcon from '@mui/icons-material/GetApp'; import DownloadIcon from '@mui/icons-material/GetApp';
import GitHubIcon from '@mui/icons-material/GitHub'; import GitHubIcon from '@mui/icons-material/GitHub';
import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone'; import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone';
import { Box, List, ListItem, ListItemAvatar, ListItemText, Link, Typography, Button } from '@mui/material'; import {
Box,
List,
ListItem,
ListItemAvatar,
ListItemText,
Link,
Typography,
Button,
ListItemButton,
Avatar
} from '@mui/material';
import { useRequest } from 'alova'; import { useRequest } from 'alova';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import type { FC } from 'react'; import type { FC } from 'react';
@@ -39,59 +49,56 @@ const Help: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SUPPORT_INFORMATION(0)} titleGutter> <SectionContent>
<List> <List sx={{ borderRadius: 3, border: '2px solid grey' }}>
<ListItem> <ListItem>
<ListItemButton component="a" href="https://emsesp.github.io/docs">
<ListItemAvatar> <ListItemAvatar>
<MenuBookIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} /> <Avatar sx={{ bgcolor: '#72caf9' }}>
<MenuBookIcon />
</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText> <ListItemText primary={LL.HELP_INFORMATION_1()} />
{LL.HELP_INFORMATION_1()}&nbsp; </ListItemButton>
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
&nbsp;
<Link target="_blank" href="https://emsesp.github.io/docs" color="primary">
{LL.CLICK_HERE()}
</Link>
</ListItemText>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemButton component="a" href="https://discord.gg/3J3GgnzpyT">
<ListItemAvatar> <ListItemAvatar>
<CommentIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} /> <Avatar sx={{ bgcolor: '#72caf9' }}>
<CommentIcon />
</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText> <ListItemText primary={LL.HELP_INFORMATION_2()} />
{LL.HELP_INFORMATION_2()}&nbsp; </ListItemButton>
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
&nbsp;
<Link target="_blank" href="https://discord.gg/3J3GgnzpyT" color="primary">
{LL.CLICK_HERE()}
</Link>
</ListItemText>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemButton component="a" href="https://github.com/emsesp/EMS-ESP32/issues/new/choose">
<ListItemAvatar> <ListItemAvatar>
<GitHubIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} /> <Avatar sx={{ bgcolor: '#72caf9' }}>
<GitHubIcon />
</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText> <ListItemText primary={LL.HELP_INFORMATION_3()} />
{LL.HELP_INFORMATION_3()}&nbsp; </ListItemButton>
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
<Link target="_blank" href="https://github.com/emsesp/EMS-ESP32/issues/new/choose" color="primary">
{LL.CLICK_HERE()}
</Link>
<br />
</ListItemText>
</ListItem> </ListItem>
</List> </List>
<Box color="warning.main"> <Box p={2} color="warning.main">
<Typography mb={1} variant="body2"> <Typography mb={1} variant="body2">
{LL.HELP_INFORMATION_4()} {LL.HELP_INFORMATION_4()}
</Typography> </Typography>
</Box> <Button
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => callAPI('system', 'info')}> startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => callAPI('system', 'info')}
>
{LL.SUPPORT_INFORMATION(0)} {LL.SUPPORT_INFORMATION(0)}
</Button> </Button>
</Box>
<Button <Button
sx={{ ml: 2 }} sx={{ ml: 2 }}
startIcon={<DownloadIcon />} startIcon={<DownloadIcon />}
@@ -102,7 +109,7 @@ const Help: FC = () => {
All Values All Values
</Button> </Button>
<Box border={1} p={1} mt={4} color="orange"> <Box border={1} p={1} mt={4}>
<Typography align="center" variant="subtitle1" color="orange"> <Typography align="center" variant="subtitle1" color="orange">
<b>{LL.HELP_INFORMATION_5()}</b> <b>{LL.HELP_INFORMATION_5()}</b>
</Typography> </Typography>

View File

@@ -11,18 +11,18 @@ import { updateState, useRequest } from 'alova';
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { useBlocker } from 'react-router-dom'; import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import SettingsSchedulerDialog from './SettingsSchedulerDialog'; import SettingsSchedulerDialog from './SchedulerDialog';
import * as EMSESP from './api'; import * as EMSESP from './api';
import { ScheduleFlag } from './types'; import { ScheduleFlag } from './types';
import { schedulerItemValidation } from './validators'; import { schedulerItemValidation } from './validators';
import type { ScheduleItem } from './types'; import type { ScheduleItem } from './types';
import type { FC } from 'react'; import type { FC } from 'react';
import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components'; import { ButtonRow, FormLoader, SectionContent, BlockNavigation, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
const SettingsScheduler: FC = () => { const Scheduler: FC = () => {
const { LL, locale } = useI18nContext(); const { LL, locale } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0); const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0); const blocker = useBlocker(numChanges !== 0);
@@ -194,6 +194,8 @@ const SettingsScheduler: FC = () => {
</> </>
); );
useLayoutTitle(LL.SCHEDULER());
return ( return (
<Table <Table
data={{ nodes: schedule.filter((si) => !si.deleted).sort((a, b) => a.time.localeCompare(b.time)) }} data={{ nodes: schedule.filter((si) => !si.deleted).sort((a, b) => a.time.localeCompare(b.time)) }}
@@ -249,7 +251,7 @@ const SettingsScheduler: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SCHEDULER()} titleGutter> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
<Box mb={2} color="warning.main"> <Box mb={2} color="warning.main">
<Typography variant="body2">{LL.SCHEDULER_HELP_1()}</Typography> <Typography variant="body2">{LL.SCHEDULER_HELP_1()}</Typography>
@@ -298,4 +300,4 @@ const SettingsScheduler: FC = () => {
); );
}; };
export default SettingsScheduler; export default Scheduler;

View File

@@ -32,7 +32,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import { updateValue } from 'utils'; import { updateValue } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
type SettingsSchedulerDialogProps = { type SchedulerDialogProps = {
open: boolean; open: boolean;
creating: boolean; creating: boolean;
onClose: () => void; onClose: () => void;
@@ -42,15 +42,7 @@ type SettingsSchedulerDialogProps = {
dow: string[]; dow: string[];
}; };
const SettingsSchedulerDialog = ({ const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, validator, dow }: SchedulerDialogProps) => {
open,
creating,
onClose,
onSave,
selectedItem,
validator,
dow
}: SettingsSchedulerDialogProps) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<ScheduleItem>(selectedItem); const [editItem, setEditItem] = useState<ScheduleItem>(selectedItem);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -246,4 +238,4 @@ const SettingsSchedulerDialog = ({
); );
}; };
export default SettingsSchedulerDialog; export default SchedulerDialog;

View File

@@ -12,20 +12,20 @@ import { useState, useContext, useEffect } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import DashboardSensorsAnalogDialog from './DashboardSensorsAnalogDialog'; import DashboardSensorsAnalogDialog from './SensorsAnalogDialog';
import DashboardSensorsTemperatureDialog from './DashboardSensorsTemperatureDialog'; import DashboardSensorsTemperatureDialog from './SensorsTemperatureDialog';
import * as EMSESP from './api'; import * as EMSESP from './api';
import { DeviceValueUOM, DeviceValueUOM_s, AnalogTypeNames, AnalogType } from './types'; import { DeviceValueUOM, DeviceValueUOM_s, AnalogTypeNames, AnalogType } from './types';
import { temperatureSensorItemValidation, analogSensorItemValidation } from './validators'; import { temperatureSensorItemValidation, analogSensorItemValidation } from './validators';
import type { TemperatureSensor, AnalogSensor } from './types'; import type { TemperatureSensor, AnalogSensor } from './types';
import type { FC } from 'react'; import type { FC } from 'react';
import { ButtonRow, SectionContent } from 'components'; import { ButtonRow, SectionContent, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
const DashboardSensors: FC = () => { const Sensors: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
const [selectedTemperatureSensor, setSelectedTemperatureSensor] = useState<TemperatureSensor>(); const [selectedTemperatureSensor, setSelectedTemperatureSensor] = useState<TemperatureSensor>();
@@ -170,6 +170,8 @@ const DashboardSensors: FC = () => {
}; };
}); });
useLayoutTitle(LL.SENSORS());
const formatDurationMin = (duration_min: number) => { const formatDurationMin = (duration_min: number) => {
const days = Math.trunc((duration_min * 60000) / 86400000); const days = Math.trunc((duration_min * 60000) / 86400000);
const hours = Math.trunc((duration_min * 60000) / 3600000) % 24; const hours = Math.trunc((duration_min * 60000) / 3600000) % 24;
@@ -406,7 +408,7 @@ const DashboardSensors: FC = () => {
); );
return ( return (
<SectionContent title={LL.SENSOR_DATA()} titleGutter> <SectionContent>
{sensorData.ts.length > 0 && ( {sensorData.ts.length > 0 && (
<> <>
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary"> <Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary">
@@ -467,4 +469,4 @@ const DashboardSensors: FC = () => {
); );
}; };
export default DashboardSensors; export default Sensors;

View File

@@ -38,7 +38,7 @@ type DashboardSensorsAnalogDialogProps = {
validator: Schema; validator: Schema;
}; };
const DashboardSensorsAnalogDialog = ({ const SensorsAnalogDialog = ({
open, open,
onClose, onClose,
onSave, onSave,
@@ -296,4 +296,4 @@ const DashboardSensorsAnalogDialog = ({
); );
}; };
export default DashboardSensorsAnalogDialog; export default SensorsAnalogDialog;

View File

@@ -26,7 +26,7 @@ import { numberValue, updateValue } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
type DashboardSensorsTemperatureDialogProps = { type SensorsTemperatureDialogProps = {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
onSave: (ts: TemperatureSensor) => void; onSave: (ts: TemperatureSensor) => void;
@@ -34,13 +34,13 @@ type DashboardSensorsTemperatureDialogProps = {
validator: Schema; validator: Schema;
}; };
const DashboardSensorsTemperatureDialog = ({ const SensorsTemperatureDialog = ({
open, open,
onClose, onClose,
onSave, onSave,
selectedItem, selectedItem,
validator validator
}: DashboardSensorsTemperatureDialogProps) => { }: SensorsTemperatureDialogProps) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const [editItem, setEditItem] = useState<TemperatureSensor>(selectedItem); const [editItem, setEditItem] = useState<TemperatureSensor>(selectedItem);
@@ -119,4 +119,4 @@ const DashboardSensorsTemperatureDialog = ({
); );
}; };
export default DashboardSensorsTemperatureDialog; export default SensorsTemperatureDialog;

View File

@@ -1,37 +0,0 @@
import { Tab } from '@mui/material';
import { Navigate, Route, Routes } from 'react-router-dom';
import SettingsApplication from './SettingsApplication';
import SettingsCustomEntities from './SettingsCustomEntities';
import SettingsCustomization from './SettingsCustomization';
import SettingsScheduler from './SettingsScheduler';
import type { FC } from 'react';
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
const Settings: FC = () => {
const { LL } = useI18nContext();
const { routerTab } = useRouterTab();
useLayoutTitle(LL.SETTINGS_OF(''));
return (
<>
<RouterTabs value={routerTab}>
<Tab value="/settings/application" label={LL.APPLICATION_SETTINGS()} />
<Tab value="/settings/customization" label={LL.CUSTOMIZATIONS()} />
<Tab value="/settings/scheduler" label={LL.SCHEDULER()} />
<Tab value="/settings/customentities" label={LL.CUSTOM_ENTITIES(0)} />
</RouterTabs>
<Routes>
<Route path="application" element={<SettingsApplication />} />
<Route path="customization" element={<SettingsCustomization />} />
<Route path="scheduler" element={<SettingsScheduler />} />
<Route path="customentities" element={<SettingsCustomEntities />} />
<Route path="*" element={<Navigate replace to="/settings/application" />} />
</Routes>
</>
);
};
export default Settings;

View File

@@ -1054,9 +1054,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@preact/preset-vite@npm:^2.8.1": "@preact/preset-vite@npm:^2.8.2":
version: 2.8.1 version: 2.8.2
resolution: "@preact/preset-vite@npm:2.8.1" resolution: "@preact/preset-vite@npm:2.8.2"
dependencies: dependencies:
"@babel/plugin-transform-react-jsx": "npm:^7.22.15" "@babel/plugin-transform-react-jsx": "npm:^7.22.15"
"@babel/plugin-transform-react-jsx-development": "npm:^7.22.5" "@babel/plugin-transform-react-jsx-development": "npm:^7.22.5"
@@ -1068,10 +1068,12 @@ __metadata:
magic-string: "npm:0.30.5" magic-string: "npm:0.30.5"
node-html-parser: "npm:^6.1.10" node-html-parser: "npm:^6.1.10"
resolve: "npm:^1.22.8" resolve: "npm:^1.22.8"
source-map: "npm:^0.7.4"
stack-trace: "npm:^1.0.0-pre2"
peerDependencies: peerDependencies:
"@babel/core": 7.x "@babel/core": 7.x
vite: 2.x || 3.x || 4.x || 5.x vite: 2.x || 3.x || 4.x || 5.x
checksum: 10/ac91fc701e078d2910b386b9e793f5429f9db04e3c56ea0f41f5f777fb21f5610acd9091def7bb2da9aaadbb9e687e1c276ff0d636fe3427ebd452dce5f98838 checksum: 10/a54b14afbd3a6a09836ec1469bc7924e128778a092cca875c3434989974023bf6f21f09d5c090d12c23fdb81ce9e6499bdf52a2f35c81141e9f2687158c56460
languageName: node languageName: node
linkType: hard linkType: hard
@@ -1394,12 +1396,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/node@npm:*, @types/node@npm:^20.11.27": "@types/node@npm:*, @types/node@npm:^20.11.28":
version: 20.11.27 version: 20.11.28
resolution: "@types/node@npm:20.11.27" resolution: "@types/node@npm:20.11.28"
dependencies: dependencies:
undici-types: "npm:~5.26.4" undici-types: "npm:~5.26.4"
checksum: 10/4be53485d499dd7c7896190e76a0ce1f6c6917d1f4d0b4b240b3670160fcbc548daed32beaac0fc92429b37dbeaa2496fc56f460acaab969bddb77394318a89b checksum: 10/b03f69213ac6e7cd5f7efa86139f24e23ff70a12fed04adeac5413b62d6982343ce94906f74c401c5afefda48d36ae0efd6a575240996b15a5cf80b456ab4221
languageName: node languageName: node
linkType: hard linkType: hard
@@ -1456,14 +1458,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/react@npm:*, @types/react@npm:^18.2.65": "@types/react@npm:*, @types/react@npm:^18.2.66":
version: 18.2.65 version: 18.2.66
resolution: "@types/react@npm:18.2.65" resolution: "@types/react@npm:18.2.66"
dependencies: dependencies:
"@types/prop-types": "npm:*" "@types/prop-types": "npm:*"
"@types/scheduler": "npm:*" "@types/scheduler": "npm:*"
csstype: "npm:^3.0.2" csstype: "npm:^3.0.2"
checksum: 10/8022689f6c68e76b5e7b3c95af794fb3d128d5b2ccac408adaa80b117724c48b04dd4a2750e5c2ca29cd70ac7719b4ed5c5b1c12cb739d6f1d52188c09fb3060 checksum: 10/8a82bda6c254681536fa8348dc15d52345d8203d5d322406feef865f74ebfe2475ebde0be4e2f9a18ffbb587dac946dfb5d0974b598779ff282259aff7e8209a
languageName: node languageName: node
linkType: hard linkType: hard
@@ -1640,12 +1642,12 @@ __metadata:
"@mui/icons-material": "npm:^5.15.13" "@mui/icons-material": "npm:^5.15.13"
"@mui/material": "npm:^5.15.13" "@mui/material": "npm:^5.15.13"
"@preact/compat": "npm:^17.1.2" "@preact/compat": "npm:^17.1.2"
"@preact/preset-vite": "npm:^2.8.1" "@preact/preset-vite": "npm:^2.8.2"
"@table-library/react-table-library": "npm:4.1.7" "@table-library/react-table-library": "npm:4.1.7"
"@types/imagemin": "npm:^8.0.5" "@types/imagemin": "npm:^8.0.5"
"@types/lodash-es": "npm:^4.17.12" "@types/lodash-es": "npm:^4.17.12"
"@types/node": "npm:^20.11.27" "@types/node": "npm:^20.11.28"
"@types/react": "npm:^18.2.65" "@types/react": "npm:^18.2.66"
"@types/react-dom": "npm:^18.2.22" "@types/react-dom": "npm:^18.2.22"
"@types/react-router-dom": "npm:^5.3.3" "@types/react-router-dom": "npm:^5.3.3"
"@typescript-eslint/eslint-plugin": "npm:^7.2.0" "@typescript-eslint/eslint-plugin": "npm:^7.2.0"
@@ -1660,7 +1662,7 @@ __metadata:
eslint-plugin-import: "npm:^2.29.1" eslint-plugin-import: "npm:^2.29.1"
eslint-plugin-jsx-a11y: "npm:^6.8.0" eslint-plugin-jsx-a11y: "npm:^6.8.0"
eslint-plugin-prettier: "npm:alpha" eslint-plugin-prettier: "npm:alpha"
eslint-plugin-react: "npm:^7.34.0" eslint-plugin-react: "npm:^7.34.1"
eslint-plugin-react-hooks: "npm:^4.6.0" eslint-plugin-react-hooks: "npm:^4.6.0"
history: "npm:^5.3.0" history: "npm:^5.3.0"
jwt-decode: "npm:^4.0.0" jwt-decode: "npm:^4.0.0"
@@ -1673,15 +1675,15 @@ __metadata:
react-dropzone: "npm:^14.2.3" react-dropzone: "npm:^14.2.3"
react-icons: "npm:^5.0.1" react-icons: "npm:^5.0.1"
react-router-dom: "npm:^6.22.3" react-router-dom: "npm:^6.22.3"
react-toastify: "npm:^10.0.4" react-toastify: "npm:^10.0.5"
rollup-plugin-visualizer: "npm:^5.12.0" rollup-plugin-visualizer: "npm:^5.12.0"
sockette: "npm:^2.0.6" sockette: "npm:^2.0.6"
terser: "npm:^5.29.1" terser: "npm:^5.29.2"
typesafe-i18n: "npm:^5.26.2" typesafe-i18n: "npm:^5.26.2"
typescript: "npm:^5.4.2" typescript: "npm:^5.4.2"
vite: "npm:^5.1.6" vite: "npm:^5.1.6"
vite-plugin-imagemin: "npm:^0.6.1" vite-plugin-imagemin: "npm:^0.6.1"
vite-tsconfig-paths: "npm:^4.3.1" vite-tsconfig-paths: "npm:^4.3.2"
languageName: unknown languageName: unknown
linkType: soft linkType: soft
@@ -3008,9 +3010,9 @@ __metadata:
linkType: hard linkType: hard
"electron-to-chromium@npm:^1.4.668": "electron-to-chromium@npm:^1.4.668":
version: 1.4.703 version: 1.4.708
resolution: "electron-to-chromium@npm:1.4.703" resolution: "electron-to-chromium@npm:1.4.708"
checksum: 10/e7927fbe75e56508dd0b4efeb0e69dfb8ee1e6e6aaf6f07c047b96ff530d8f49e1eaf51cae64c2d3c179e3932fb37661012ccaa4f36956dd96480219f3a23013 checksum: 10/a051ea46f9cddbda4218edfff69cdc8007a50554f4875d09706d43d7c1641267e9f81394c07f04e2d0616e989b227fe5ef36433a8b5bcfbb2185a84ebf346334
languageName: node languageName: node
linkType: hard linkType: hard
@@ -3686,9 +3688,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"eslint-plugin-react@npm:^7.34.0": "eslint-plugin-react@npm:^7.34.1":
version: 7.34.0 version: 7.34.1
resolution: "eslint-plugin-react@npm:7.34.0" resolution: "eslint-plugin-react@npm:7.34.1"
dependencies: dependencies:
array-includes: "npm:^3.1.7" array-includes: "npm:^3.1.7"
array.prototype.findlast: "npm:^1.2.4" array.prototype.findlast: "npm:^1.2.4"
@@ -3710,7 +3712,7 @@ __metadata:
string.prototype.matchall: "npm:^4.0.10" string.prototype.matchall: "npm:^4.0.10"
peerDependencies: peerDependencies:
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
checksum: 10/e09623d715e25e012cc442648616ea5f8029c17a397e7b4f54c47da7cc4edb0ffec91af3269c259c1a92b8d83802b10f9c7148280a0c8d7659b15724ee8b50d8 checksum: 10/ee059971065ea7e73ab5d8728774235c7dbf7a5e9f937c3b47e97f8fa9a5a96ab511d2ae6d5ec76a7e705ca666673d454f1e75a94033720819d041827f50f9c8
languageName: node languageName: node
linkType: hard linkType: hard
@@ -6858,15 +6860,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-toastify@npm:^10.0.4": "react-toastify@npm:^10.0.5":
version: 10.0.4 version: 10.0.5
resolution: "react-toastify@npm:10.0.4" resolution: "react-toastify@npm:10.0.5"
dependencies: dependencies:
clsx: "npm:^2.1.0" clsx: "npm:^2.1.0"
peerDependencies: peerDependencies:
react: ">=16" react: ">=18"
react-dom: ">=16" react-dom: ">=18"
checksum: 10/57f4d0032bf328381bdfeb78ab5efa988d425627a61ffa43b0caa184633a0ea44253a349d6b967247fa3d480ad82a2bbaa9063ce3f89be9550eb9b30398a6837 checksum: 10/6630f4b6d6902d827efd5e66c09df693c7ab8abeeb6ef24d880080f47b636614ef9cc251dd5e6564d49fe2f6f25f720ce0f7ef72cd4b0cd58a65b7c4b8052fac
languageName: node languageName: node
linkType: hard linkType: hard
@@ -7605,6 +7607,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"stack-trace@npm:^1.0.0-pre2":
version: 1.0.0-pre2
resolution: "stack-trace@npm:1.0.0-pre2"
checksum: 10/a64099f86acc01980b0a7fbc662f3233bf8626daf95c53e31c835b2252ae11fc3dbfe8f3e77a7f8310132dd488af2795057cd7db599de0c41a6fa99b16068273
languageName: node
linkType: hard
"strict-uri-encode@npm:^1.0.0": "strict-uri-encode@npm:^1.0.0":
version: 1.1.0 version: 1.1.0
resolution: "strict-uri-encode@npm:1.1.0" resolution: "strict-uri-encode@npm:1.1.0"
@@ -7928,9 +7937,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"terser@npm:^5.29.1": "terser@npm:^5.29.2":
version: 5.29.1 version: 5.29.2
resolution: "terser@npm:5.29.1" resolution: "terser@npm:5.29.2"
dependencies: dependencies:
"@jridgewell/source-map": "npm:^0.3.3" "@jridgewell/source-map": "npm:^0.3.3"
acorn: "npm:^8.8.2" acorn: "npm:^8.8.2"
@@ -7938,7 +7947,7 @@ __metadata:
source-map-support: "npm:~0.5.20" source-map-support: "npm:~0.5.20"
bin: bin:
terser: bin/terser terser: bin/terser
checksum: 10/e8c036e7cd7d9e988765272453acdc52a019827e10710cf109c86d6f31248c8d4d8aa3a3deef30f931a2bb75a8ffc731ca947bd126e7d4e6dda157e6fe892ac0 checksum: 10/062df6a8f99ea2635d1b3ce41cfd4180dea6e1c83db9b2cf4b525170b2446d10e069d2877d8dcb59fbf6045870efa17b56462b67045ef2d2b420870f9d144690
languageName: node languageName: node
linkType: hard linkType: hard
@@ -8020,7 +8029,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"tsconfck@npm:^3.0.1": "tsconfck@npm:^3.0.3":
version: 3.0.3 version: 3.0.3
resolution: "tsconfck@npm:3.0.3" resolution: "tsconfck@npm:3.0.3"
peerDependencies: peerDependencies:
@@ -8329,19 +8338,19 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"vite-tsconfig-paths@npm:^4.3.1": "vite-tsconfig-paths@npm:^4.3.2":
version: 4.3.1 version: 4.3.2
resolution: "vite-tsconfig-paths@npm:4.3.1" resolution: "vite-tsconfig-paths@npm:4.3.2"
dependencies: dependencies:
debug: "npm:^4.1.1" debug: "npm:^4.1.1"
globrex: "npm:^0.1.2" globrex: "npm:^0.1.2"
tsconfck: "npm:^3.0.1" tsconfck: "npm:^3.0.3"
peerDependencies: peerDependencies:
vite: "*" vite: "*"
peerDependenciesMeta: peerDependenciesMeta:
vite: vite:
optional: true optional: true
checksum: 10/1432f80750f5cbe181c265eb9fc2e9fff8b25a2858f176dc0a02311e3e826333526ee9c16bb0aaaa8555a417ea944d68a2e8225181215cd9502370f913eb3f79 checksum: 10/c12e2087fd01ac8a694850c649b79d5b9798cdba0ef9ab4116f669d8ffa1a9a3195c5a14410d3d9a12d2f08cd35ddd74f03d9c7b13a2d590d002055cdaab45c0
languageName: node languageName: node
linkType: hard linkType: hard