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

View File

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

View File

@@ -1,61 +1,63 @@
import { Navigate, Routes, Route } from 'react-router-dom';
import Dashboard from './project/Dashboard';
import Help from './project/Help';
import Settings from './project/Settings';
import type { FC } from 'react';
import { Layout, RequireAdmin } from 'components';
import Settings from 'framework/Settings';
import AccessPoint from 'framework/ap/AccessPoint';
import Mqtt from 'framework/mqtt/Mqtt';
import NetworkConnection from 'framework/network/NetworkConnection';
import NetworkTime from 'framework/ntp/NetworkTime';
import OTASettingsForm from 'framework/ota/OTASettingsForm';
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 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>
<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
path="/settings/*"
path="settings/*"
element={
<RequireAdmin>
<Settings />
</RequireAdmin>
}
/>
<Route path="/help/*" element={<Help />} />
<Route path="/network/*" element={<NetworkConnection />} />
<Route path="/ap/*" element={<AccessPoint />} />
<Route path="/ntp/*" element={<NetworkTime />} />
<Route path="/mqtt/*" element={<Mqtt />} />
<Route path="/settings/network/*" element={<NetworkConnection />} />
<Route path="/settings/ems-esp/*" element={<ApplicationSettings />} />
<Route path="/settings/ap/*" element={<AccessPoint />} />
<Route path="/settings/ntp/*" element={<NetworkTime />} />
<Route path="/settings/mqtt/*" element={<Mqtt />} />
<Route path="/settings/ota/*" element={<OTASettingsForm />} />
<Route
path="/security/*"
path="/settings/security/*"
element={
<RequireAdmin>
<Security />
</RequireAdmin>
}
/>
<Route path="/system/*" element={<System />} />
<Route path="/settings/upload/*" element={<UploadDownload />} />
<Route path="/help/*" element={<Help />} />
<Route path="/*" element={<Navigate to="/" />} />
</Routes>
</Layout>

View File

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

View File

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

View File

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

View File

@@ -1,53 +1,188 @@
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import DashboardIcon from '@mui/icons-material/Dashboard';
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import AssessmentIcon from '@mui/icons-material/Assessment';
import CategoryIcon from '@mui/icons-material/Category';
import ConstructionIcon from '@mui/icons-material/Construction';
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 SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import TuneIcon from '@mui/icons-material/Tune';
import { Divider, List } from '@mui/material';
import { useContext } from 'react';
import type { FC } from 'react';
import {
Divider,
List,
Box,
Button,
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 { 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 authenticatedContext = useContext(AuthenticatedContext);
const { LL } = useI18nContext();
const { me, signOut } = useContext(AuthenticatedContext);
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 (
<>
<List disablePadding component="nav">
<LayoutMenuItem icon={DashboardIcon} label={LL.DASHBOARD()} to={`/dashboard`} />
<LayoutMenuItem
icon={TuneIcon}
label={LL.SETTINGS_OF('')}
to={`/settings`}
disabled={!authenticatedContext.me.admin}
/>
<LayoutMenuItem icon={InfoIcon} label={LL.HELP_OF('')} to={`/help`} />
<List component="nav">
<LayoutMenuItem icon={CategoryIcon} label={LL.DEVICES()} to={`/devices`} />
<LayoutMenuItem icon={SensorsIcon} label={LL.SENSORS()} to={`/sensors`} />
<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 disablePadding component="nav">
<LayoutMenuItem icon={SettingsEthernetIcon} label={LL.NETWORK(0)} to="/network" />
<LayoutMenuItem icon={SettingsInputAntennaIcon} label={LL.ACCESS_POINT(0)} to="/ap" />
<LayoutMenuItem icon={AccessTimeIcon} label="NTP" to="/ntp" />
<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 style={{ marginTop: `auto` }}>
<LayoutMenuItem icon={AssessmentIcon} label={LL.STATUS_OF('')} to="/status" />
<LayoutMenuItem icon={SettingsIcon} label={LL.SETTINGS(0)} disabled={!me.admin} to="/settings" />
<LayoutMenuItem icon={InfoIcon} label={LL.HELP_OF('')} to={`/help`} />
</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 = () => {
const loc = useLocation().pathname;
const routerTab = loc.substring(0, loc.lastIndexOf('/')) ? loc : false;
// const routerTabPath = useResolvedPath(':tab');
// const routerTabPathMatch = useMatch(routerTabPath.pathname);
// const routerTab = routerTabPathMatch?.params?.tab || false;
const routerTabPathMatch = useMatch(useResolvedPath(':tab').pathname);
const routerTab = routerTabPathMatch?.params?.tab || false;
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 (
<SectionContent title={LL.SETTINGS_OF(LL.ACCESS_POINT(1))} titleGutter>
<SectionContent title={LL.SETTINGS_OF(LL.ACCESS_POINT(1))}>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()}
</SectionContent>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -96,7 +96,7 @@ const SecuritySettingsForm: FC = () => {
};
return (
<SectionContent title={LL.SETTINGS_OF(LL.SECURITY(1))} titleGutter>
<SectionContent title={LL.SETTINGS_OF(LL.SECURITY(1))}>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()}
</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 (
<SectionContent title={LL.STATUS_OF(LL.SYSTEM(1))} titleGutter>
<SectionContent title={LL.STATUS_OF(LL.SYSTEM(1))}>
{restarting ? <RestartMonitor /> : content()}
{data && (
<SystemStatusVersionDialog

View File

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

View File

@@ -12,7 +12,6 @@ const de: Translation = {
USERNAME: 'Nutzername',
PASSWORD: 'Passwort',
SU_PASSWORD: 'su Passwort',
DASHBOARD: 'Kontrollzentrum',
SETTINGS_OF: '{0} Einstellungen',
HELP_OF: '{0} Hilfe',
LOGGED_IN: 'Eingeloggt als {name}',
@@ -37,8 +36,6 @@ const de: Translation = {
BRAND: 'Marke',
ENTITY_NAME: 'Entitätsname',
VALUE: '{{Wert|wert}}',
DEVICE_DATA: 'Gerätedaten',
SENSOR_DATA: 'Sensordaten',
DEVICES: 'Geräte',
SENSORS: 'Sensoren',
RUN_COMMAND: 'Befehl ausführen',
@@ -163,9 +160,7 @@ const de: Translation = {
OPTIONS: 'Optionen',
NAME: 'Name',
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',
CLICK_HERE: 'Hier klicken',
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_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',
PASSWORD: 'Password',
SU_PASSWORD: 'su Password',
DASHBOARD: 'Dashboard',
SETTINGS_OF: '{0} Settings',
HELP_OF: '{0} Help',
LOGGED_IN: 'Logged in as {name}',
@@ -37,8 +36,6 @@ const en: Translation = {
BRAND: 'Brand',
ENTITY_NAME: 'Entity Name',
VALUE: '{{Value|value}}',
DEVICE_DATA: 'Device Data',
SENSOR_DATA: 'Sensor Data',
DEVICES: 'Devices',
SENSORS: 'Sensors',
RUN_COMMAND: 'Call Command',
@@ -163,9 +160,7 @@ const en: Translation = {
OPTIONS: 'Options',
NAME: 'Name',
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',
CLICK_HERE: 'Click Here',
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_3: 'To request a feature or report a bug',

View File

@@ -12,7 +12,6 @@ const fr: Translation = {
USERNAME: 'Nom d\'utilisateur',
PASSWORD: 'Mot de passe',
SU_PASSWORD: 'Mot de passe su',
DASHBOARD: 'Tableau de bord',
SETTINGS_OF: 'Paramètres {0}',
HELP_OF: 'Aide {0}',
LOGGED_IN: 'Connecté en tant que {name}',
@@ -37,8 +36,6 @@ const fr: Translation = {
BRAND: 'Marque',
ENTITY_NAME: 'Nom de l\'entité',
VALUE: 'Valeur',
DEVICE_DATA: 'Données des appareils',
SENSOR_DATA: 'Données des capteurs',
DEVICES: 'Appareils',
SENSORS: 'Capteurs',
RUN_COMMAND: 'Lancer une commande',
@@ -163,9 +160,7 @@ const fr: Translation = {
OPTIONS: 'Options',
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 ?',
DEVICE_ENTITIES: 'Entités de l\'appareil',
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_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',

View File

@@ -12,7 +12,6 @@ const it: Translation = {
USERNAME: 'Nome Utente',
PASSWORD: 'Password',
SU_PASSWORD: 'su Password',
DASHBOARD: 'Pannello di Controllo',
SETTINGS_OF: 'Impostazioni {0}',
HELP_OF: '{0} Aiuto',
LOGGED_IN: 'Registrato come {name}',
@@ -37,8 +36,6 @@ const it: Translation = {
BRAND: 'Marca',
ENTITY_NAME: 'Nome Entità',
VALUE: '{{Valore|valore}}',
DEVICE_DATA: 'Device Data',
SENSOR_DATA: 'Sensor Data',
DEVICES: 'Dispositivi',
SENSORS: 'Sensori',
RUN_COMMAND: 'Esegui',
@@ -165,9 +162,7 @@ const it: Translation = {
OPTIONS: 'Opzioni',
NAME: 'Nome',
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',
CLICK_HERE: 'Clicca qui',
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_3: 'Per richiedere una funzionalità o segnalare un errore',

View File

@@ -12,7 +12,6 @@ const nl: Translation = {
USERNAME: 'Gebruikersnaam',
PASSWORD: 'Wachtwoord',
SU_PASSWORD: 'su Wachtwoord',
DASHBOARD: 'Dashboard',
SETTINGS_OF: '{0} Instellingen',
HELP_OF: '{0} Help',
LOGGED_IN: 'Ingelogd als {name}',
@@ -37,8 +36,6 @@ const nl: Translation = {
BRAND: 'Merk',
ENTITY_NAME: 'Entiteit',
VALUE: '{{Waarde|waarde}}',
SENSOR_DATA: 'Sensor data',
DEVICE_DATA: 'Apparaat data',
DEVICES: 'Apparaten',
SENSORS: 'Sensoren',
RUN_COMMAND: 'Call commando',
@@ -163,9 +160,7 @@ const nl: Translation = {
OPTIONS: 'Opties',
NAME: 'Naam',
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',
CLICK_HERE: 'Klik Hier',
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_3: 'Om een nieuwe feature te vragen of een bug te rapporteren',

View File

@@ -12,7 +12,6 @@ const no: Translation = {
USERNAME: 'Brukernavn',
PASSWORD: 'Passord',
SU_PASSWORD: 'su Passord',
DASHBOARD: 'Dashboard',
SETTINGS_OF: '{0} Innstillinger',
HELP_OF: '{0} Hjelp',
LOGGED_IN: 'Logget in som {name}',
@@ -37,8 +36,6 @@ const no: Translation = {
BRAND: 'Fabrikat',
ENTITY_NAME: 'Objektsnavn',
VALUE: '{{Verdi|verdi}}',
DEVICE_DATA: 'Enheterdata',
SENSOR_DATA: 'Sensordata',
DEVICES: 'Enheter',
SENSORS: 'Sensorer',
RUN_COMMAND: 'Kjør kommando',
@@ -163,9 +160,7 @@ const no: Translation = {
OPTIONS: 'Alternativ',
NAME: 'Navn',
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',
CLICK_HERE: 'Klikk her',
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_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|}}',
PASSWORD: 'Hasło',
SU_PASSWORD: 'Hasło "su"',
DASHBOARD: 'Pulpit',
SETTINGS_OF: 'Ustawienia {0}',
HELP_OF: 'Pomoc {0}',
LOGGED_IN: 'Zalogowano użytkownika {name}.',
@@ -37,8 +36,6 @@ const pl: BaseTranslation = {
VERSION: 'Wersja',
ENTITY_NAME: '{{N|n|}}azwa encji',
VALUE: '{{W|w|}}artość',
DEVICE_DATA: 'Dane z urządzeń',
SENSOR_DATA: 'Dane z czujników',
DEVICES: 'Urządzenia',
SENSORS: 'Czujniki',
RUN_COMMAND: 'Wykonaj komendę',
@@ -163,9 +160,7 @@ const pl: BaseTranslation = {
OPTIONS: 'Opcje',
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?',
DEVICE_ENTITIES: 'Encje urządzenia',
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_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',

View File

@@ -12,7 +12,6 @@ const sk: Translation = {
USERNAME: 'Užívateľské meno',
PASSWORD: 'Heslo',
SU_PASSWORD: 'su heslo',
DASHBOARD: 'Panel',
SETTINGS_OF: '{0} Nastavenia',
HELP_OF: '{0} Pomoc',
LOGGED_IN: 'Prihlásený ako {name}',
@@ -37,8 +36,6 @@ const sk: Translation = {
BRAND: 'Značka',
ENTITY_NAME: 'Názov entity',
VALUE: '{{Value|value}}',
DEVICE_DATA: 'Dáta zariadenia',
SENSOR_DATA: 'Dáta snímača',
DEVICES: 'Zariadenia',
SENSORS: 'Snímače',
RUN_COMMAND: 'Volať príkaz',
@@ -163,9 +160,7 @@ const sk: Translation = {
OPTIONS: 'Možnosti',
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?',
DEVICE_ENTITIES: 'Entity zariadenia',
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_2: 'Pre živý komunitný chat sa pripojte na náš Discord server',
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',
PASSWORD: 'Lösenord',
SU_PASSWORD: 'su Lösenord',
DASHBOARD: 'Kontrollpanel',
SETTINGS_OF: '{0} Inställningar',
HELP_OF: '{0} Hjälp',
LOGGED_IN: 'Inloggad som {name}',
@@ -37,8 +36,6 @@ const sv: Translation = {
BRAND: 'Fabrikat',
ENTITY_NAME: 'Entitetsnamn',
VALUE: '{{Värde|värde}}',
DEVICE_DATA: 'Enhets data',
SENSOR_DATA: 'Sensor data',
DEVICES: 'Enheter',
SENSORS: 'Sensorer',
RUN_COMMAND: 'Kör Kommando',
@@ -163,9 +160,7 @@ const sv: Translation = {
OPTIONS: 'Alternativ',
NAME: 'Namn',
CUSTOMIZATIONS_RESET: 'Är du säker på att du vill ta bort alla anpassningar inklusive inställningar för Temperatur och Analoga sensorer?',
DEVICE_ENTITIES: 'Enhets-entiteter',
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_2: 'För community-support besök vår Discord-server',
HELP_INFORMATION_3: 'Önska en ny funktion eller rapportera en bugg',

View File

@@ -12,7 +12,6 @@ const tr: Translation = {
USERNAME: 'Kullanıcı Adı',
PASSWORD: 'Şifre',
SU_PASSWORD: 'SK Şifresi',
DASHBOARD: 'Gösterge Paneli',
SETTINGS_OF: '{0} Ayarlar',
HELP_OF: '{0} Yardım',
LOGGED_IN: '{name} olarak giriş yapıldı',
@@ -37,8 +36,6 @@ const tr: Translation = {
BRAND: 'Marka',
ENTITY_NAME: 'Valık Adı',
VALUE: '{{Değer|değer}}',
DEVICE_DATA: 'Cihaz Bilgisi',
SENSOR_DATA: 'Sensör Bilgisi',
DEVICES: 'Cihazlar',
SENSORS: 'Sensörler',
RUN_COMMAND: 'Çalıştırma Komutu',
@@ -163,9 +160,7 @@ const tr: Translation = {
OPTIONS: 'Seçenekler',
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?',
DEVICE_ENTITIES: 'Cihaz Varlıkları',
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_2: 'Canlı topluluk sohbeti için Discord sunucumuza katılın',
HELP_INFORMATION_3: 'Yeni bir özellik talep etmek yada hata bildirmek için',

View File

@@ -20,7 +20,8 @@ import {
ValidatedTextField,
ButtonRow,
MessageBox,
BlockNavigation
BlockNavigation,
useLayoutTitle
} from 'components';
import RestartMonitor from 'framework/system/RestartMonitor';
@@ -36,7 +37,7 @@ export function boardProfileSelectItems() {
));
}
const SettingsApplication: FC = () => {
const ApplicationSettings: FC = () => {
const {
loadData,
saveData,
@@ -97,6 +98,8 @@ const SettingsApplication: FC = () => {
});
};
useLayoutTitle(LL.APPLICATION_SETTINGS());
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
@@ -680,11 +683,11 @@ const SettingsApplication: FC = () => {
};
return (
<SectionContent title={LL.APPLICATION_SETTINGS()} titleGutter>
<SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <RestartMonitor /> : content()}
</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 SettingsCustomEntitiesDialog from './SettingsCustomEntitiesDialog';
import SettingsCustomEntitiesDialog from './CustomEntitiesDialog';
import * as EMSESP from './api';
import { DeviceValueTypeNames, DeviceValueUOM_s } from './types';
import { entityItemValidation } from './validators';
import type { EntityItem } from './types';
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';
const SettingsCustomEntities: FC = () => {
const CustomEntities: FC = () => {
const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0);
@@ -31,6 +31,8 @@ const SettingsCustomEntities: FC = () => {
const [creating, setCreating] = useState<boolean>(false);
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
useLayoutTitle(LL.CUSTOM_ENTITIES(0));
const {
data: entities,
send: fetchEntities,
@@ -246,7 +248,7 @@ const SettingsCustomEntities: FC = () => {
};
return (
<SectionContent title={LL.CUSTOM_ENTITIES(0)} titleGutter>
<SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
<Box mb={2} color="warning.main">
<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 { validate } from 'validators';
type SettingsCustomEntitiesDialogProps = {
type CustomEntitiesDialogProps = {
open: boolean;
creating: boolean;
onClose: () => void;
@@ -39,14 +39,14 @@ type SettingsCustomEntitiesDialogProps = {
validator: Schema;
};
const SettingsCustomEntitiesDialog = ({
const CustomEntitiesDialog = ({
open,
creating,
onClose,
onSave,
selectedItem,
validator
}: SettingsCustomEntitiesDialogProps) => {
}: CustomEntitiesDialogProps) => {
const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<EntityItem>(selectedItem);
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 { toast } from 'react-toastify';
import SettingsCustomizationDialog from './CustomizationDialog';
import EntityMaskToggle from './EntityMaskToggle';
import OptionIcon from './OptionIcon';
import SettingsCustomizationDialog from './SettingsCustomizationDialog';
import * as EMSESP from './api';
@@ -37,14 +37,14 @@ import type { DeviceShort, DeviceEntity } from './types';
import type { FC } from 'react';
import { dialogStyle } from 'CustomTheme';
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 { useI18nContext } from 'i18n/i18n-react';
export const APIURL = window.location.origin + '/api/';
const SettingsCustomization: FC = () => {
const Customization: FC = () => {
const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0);
@@ -58,6 +58,8 @@ const SettingsCustomization: FC = () => {
const [selectedDeviceEntity, setSelectedDeviceEntity] = useState<DeviceEntity>();
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
useLayoutTitle(LL.CUSTOMIZATIONS());
// fetch devices first
const { data: devices } = useRequest(EMSESP.readDevices);
@@ -508,9 +510,6 @@ const SettingsCustomization: FC = () => {
const renderContent = () => (
<>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.DEVICE_ENTITIES()}
</Typography>
{devices && renderDeviceList()}
{deviceEntities && renderDeviceData()}
{restartNeeded && (
@@ -561,7 +560,7 @@ const SettingsCustomization: FC = () => {
);
return (
<SectionContent title={LL.CUSTOMIZATIONS()} titleGutter>
<SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <RestartMonitor /> : renderContent()}
{selectedDeviceEntity && (
@@ -576,4 +575,4 @@ const SettingsCustomization: FC = () => {
);
};
export default SettingsCustomization;
export default Customization;

View File

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

View File

@@ -40,7 +40,7 @@ type DashboardDevicesDialogProps = {
progress: boolean;
};
const DashboardDevicesDialog = ({
const DevicesDialog = ({
open,
onClose,
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 { LL } = useI18nContext();
@@ -272,11 +272,7 @@ const DashboardStatus: FC = () => {
);
};
return (
<SectionContent title={LL.EMS_BUS_STATUS_TITLE()} titleGutter>
{content()}
</SectionContent>
);
return <SectionContent title={LL.EMS_BUS_STATUS_TITLE()}>{content()}</SectionContent>;
};
export default DashboardStatus;
export default EMSStatus;

View File

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

View File

@@ -11,18 +11,18 @@ import { updateState, useRequest } from 'alova';
import { useState, useEffect, useCallback } from 'react';
import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify';
import SettingsSchedulerDialog from './SettingsSchedulerDialog';
import SettingsSchedulerDialog from './SchedulerDialog';
import * as EMSESP from './api';
import { ScheduleFlag } from './types';
import { schedulerItemValidation } from './validators';
import type { ScheduleItem } from './types';
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';
const SettingsScheduler: FC = () => {
const Scheduler: FC = () => {
const { LL, locale } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0);
@@ -194,6 +194,8 @@ const SettingsScheduler: FC = () => {
</>
);
useLayoutTitle(LL.SCHEDULER());
return (
<Table
data={{ nodes: schedule.filter((si) => !si.deleted).sort((a, b) => a.time.localeCompare(b.time)) }}
@@ -249,7 +251,7 @@ const SettingsScheduler: FC = () => {
};
return (
<SectionContent title={LL.SCHEDULER()} titleGutter>
<SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
<Box mb={2} color="warning.main">
<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 { validate } from 'validators';
type SettingsSchedulerDialogProps = {
type SchedulerDialogProps = {
open: boolean;
creating: boolean;
onClose: () => void;
@@ -42,15 +42,7 @@ type SettingsSchedulerDialogProps = {
dow: string[];
};
const SettingsSchedulerDialog = ({
open,
creating,
onClose,
onSave,
selectedItem,
validator,
dow
}: SettingsSchedulerDialogProps) => {
const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, validator, dow }: SchedulerDialogProps) => {
const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<ScheduleItem>(selectedItem);
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 DashboardSensorsAnalogDialog from './DashboardSensorsAnalogDialog';
import DashboardSensorsTemperatureDialog from './DashboardSensorsTemperatureDialog';
import DashboardSensorsAnalogDialog from './SensorsAnalogDialog';
import DashboardSensorsTemperatureDialog from './SensorsTemperatureDialog';
import * as EMSESP from './api';
import { DeviceValueUOM, DeviceValueUOM_s, AnalogTypeNames, AnalogType } from './types';
import { temperatureSensorItemValidation, analogSensorItemValidation } from './validators';
import type { TemperatureSensor, AnalogSensor } from './types';
import type { FC } from 'react';
import { ButtonRow, SectionContent } from 'components';
import { ButtonRow, SectionContent, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
const DashboardSensors: FC = () => {
const Sensors: FC = () => {
const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext);
const [selectedTemperatureSensor, setSelectedTemperatureSensor] = useState<TemperatureSensor>();
@@ -170,6 +170,8 @@ const DashboardSensors: FC = () => {
};
});
useLayoutTitle(LL.SENSORS());
const formatDurationMin = (duration_min: number) => {
const days = Math.trunc((duration_min * 60000) / 86400000);
const hours = Math.trunc((duration_min * 60000) / 3600000) % 24;
@@ -406,7 +408,7 @@ const DashboardSensors: FC = () => {
);
return (
<SectionContent title={LL.SENSOR_DATA()} titleGutter>
<SectionContent>
{sensorData.ts.length > 0 && (
<>
<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;
};
const DashboardSensorsAnalogDialog = ({
const SensorsAnalogDialog = ({
open,
onClose,
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';
type DashboardSensorsTemperatureDialogProps = {
type SensorsTemperatureDialogProps = {
open: boolean;
onClose: () => void;
onSave: (ts: TemperatureSensor) => void;
@@ -34,13 +34,13 @@ type DashboardSensorsTemperatureDialogProps = {
validator: Schema;
};
const DashboardSensorsTemperatureDialog = ({
const SensorsTemperatureDialog = ({
open,
onClose,
onSave,
selectedItem,
validator
}: DashboardSensorsTemperatureDialogProps) => {
}: SensorsTemperatureDialogProps) => {
const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
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
linkType: hard
"@preact/preset-vite@npm:^2.8.1":
version: 2.8.1
resolution: "@preact/preset-vite@npm:2.8.1"
"@preact/preset-vite@npm:^2.8.2":
version: 2.8.2
resolution: "@preact/preset-vite@npm:2.8.2"
dependencies:
"@babel/plugin-transform-react-jsx": "npm:^7.22.15"
"@babel/plugin-transform-react-jsx-development": "npm:^7.22.5"
@@ -1068,10 +1068,12 @@ __metadata:
magic-string: "npm:0.30.5"
node-html-parser: "npm:^6.1.10"
resolve: "npm:^1.22.8"
source-map: "npm:^0.7.4"
stack-trace: "npm:^1.0.0-pre2"
peerDependencies:
"@babel/core": 7.x
vite: 2.x || 3.x || 4.x || 5.x
checksum: 10/ac91fc701e078d2910b386b9e793f5429f9db04e3c56ea0f41f5f777fb21f5610acd9091def7bb2da9aaadbb9e687e1c276ff0d636fe3427ebd452dce5f98838
checksum: 10/a54b14afbd3a6a09836ec1469bc7924e128778a092cca875c3434989974023bf6f21f09d5c090d12c23fdb81ce9e6499bdf52a2f35c81141e9f2687158c56460
languageName: node
linkType: hard
@@ -1394,12 +1396,12 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:*, @types/node@npm:^20.11.27":
version: 20.11.27
resolution: "@types/node@npm:20.11.27"
"@types/node@npm:*, @types/node@npm:^20.11.28":
version: 20.11.28
resolution: "@types/node@npm:20.11.28"
dependencies:
undici-types: "npm:~5.26.4"
checksum: 10/4be53485d499dd7c7896190e76a0ce1f6c6917d1f4d0b4b240b3670160fcbc548daed32beaac0fc92429b37dbeaa2496fc56f460acaab969bddb77394318a89b
checksum: 10/b03f69213ac6e7cd5f7efa86139f24e23ff70a12fed04adeac5413b62d6982343ce94906f74c401c5afefda48d36ae0efd6a575240996b15a5cf80b456ab4221
languageName: node
linkType: hard
@@ -1456,14 +1458,14 @@ __metadata:
languageName: node
linkType: hard
"@types/react@npm:*, @types/react@npm:^18.2.65":
version: 18.2.65
resolution: "@types/react@npm:18.2.65"
"@types/react@npm:*, @types/react@npm:^18.2.66":
version: 18.2.66
resolution: "@types/react@npm:18.2.66"
dependencies:
"@types/prop-types": "npm:*"
"@types/scheduler": "npm:*"
csstype: "npm:^3.0.2"
checksum: 10/8022689f6c68e76b5e7b3c95af794fb3d128d5b2ccac408adaa80b117724c48b04dd4a2750e5c2ca29cd70ac7719b4ed5c5b1c12cb739d6f1d52188c09fb3060
checksum: 10/8a82bda6c254681536fa8348dc15d52345d8203d5d322406feef865f74ebfe2475ebde0be4e2f9a18ffbb587dac946dfb5d0974b598779ff282259aff7e8209a
languageName: node
linkType: hard
@@ -1640,12 +1642,12 @@ __metadata:
"@mui/icons-material": "npm:^5.15.13"
"@mui/material": "npm:^5.15.13"
"@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"
"@types/imagemin": "npm:^8.0.5"
"@types/lodash-es": "npm:^4.17.12"
"@types/node": "npm:^20.11.27"
"@types/react": "npm:^18.2.65"
"@types/node": "npm:^20.11.28"
"@types/react": "npm:^18.2.66"
"@types/react-dom": "npm:^18.2.22"
"@types/react-router-dom": "npm:^5.3.3"
"@typescript-eslint/eslint-plugin": "npm:^7.2.0"
@@ -1660,7 +1662,7 @@ __metadata:
eslint-plugin-import: "npm:^2.29.1"
eslint-plugin-jsx-a11y: "npm:^6.8.0"
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"
history: "npm:^5.3.0"
jwt-decode: "npm:^4.0.0"
@@ -1673,15 +1675,15 @@ __metadata:
react-dropzone: "npm:^14.2.3"
react-icons: "npm:^5.0.1"
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"
sockette: "npm:^2.0.6"
terser: "npm:^5.29.1"
terser: "npm:^5.29.2"
typesafe-i18n: "npm:^5.26.2"
typescript: "npm:^5.4.2"
vite: "npm:^5.1.6"
vite-plugin-imagemin: "npm:^0.6.1"
vite-tsconfig-paths: "npm:^4.3.1"
vite-tsconfig-paths: "npm:^4.3.2"
languageName: unknown
linkType: soft
@@ -3008,9 +3010,9 @@ __metadata:
linkType: hard
"electron-to-chromium@npm:^1.4.668":
version: 1.4.703
resolution: "electron-to-chromium@npm:1.4.703"
checksum: 10/e7927fbe75e56508dd0b4efeb0e69dfb8ee1e6e6aaf6f07c047b96ff530d8f49e1eaf51cae64c2d3c179e3932fb37661012ccaa4f36956dd96480219f3a23013
version: 1.4.708
resolution: "electron-to-chromium@npm:1.4.708"
checksum: 10/a051ea46f9cddbda4218edfff69cdc8007a50554f4875d09706d43d7c1641267e9f81394c07f04e2d0616e989b227fe5ef36433a8b5bcfbb2185a84ebf346334
languageName: node
linkType: hard
@@ -3686,9 +3688,9 @@ __metadata:
languageName: node
linkType: hard
"eslint-plugin-react@npm:^7.34.0":
version: 7.34.0
resolution: "eslint-plugin-react@npm:7.34.0"
"eslint-plugin-react@npm:^7.34.1":
version: 7.34.1
resolution: "eslint-plugin-react@npm:7.34.1"
dependencies:
array-includes: "npm:^3.1.7"
array.prototype.findlast: "npm:^1.2.4"
@@ -3710,7 +3712,7 @@ __metadata:
string.prototype.matchall: "npm:^4.0.10"
peerDependencies:
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
checksum: 10/e09623d715e25e012cc442648616ea5f8029c17a397e7b4f54c47da7cc4edb0ffec91af3269c259c1a92b8d83802b10f9c7148280a0c8d7659b15724ee8b50d8
checksum: 10/ee059971065ea7e73ab5d8728774235c7dbf7a5e9f937c3b47e97f8fa9a5a96ab511d2ae6d5ec76a7e705ca666673d454f1e75a94033720819d041827f50f9c8
languageName: node
linkType: hard
@@ -6858,15 +6860,15 @@ __metadata:
languageName: node
linkType: hard
"react-toastify@npm:^10.0.4":
version: 10.0.4
resolution: "react-toastify@npm:10.0.4"
"react-toastify@npm:^10.0.5":
version: 10.0.5
resolution: "react-toastify@npm:10.0.5"
dependencies:
clsx: "npm:^2.1.0"
peerDependencies:
react: ">=16"
react-dom: ">=16"
checksum: 10/57f4d0032bf328381bdfeb78ab5efa988d425627a61ffa43b0caa184633a0ea44253a349d6b967247fa3d480ad82a2bbaa9063ce3f89be9550eb9b30398a6837
react: ">=18"
react-dom: ">=18"
checksum: 10/6630f4b6d6902d827efd5e66c09df693c7ab8abeeb6ef24d880080f47b636614ef9cc251dd5e6564d49fe2f6f25f720ce0f7ef72cd4b0cd58a65b7c4b8052fac
languageName: node
linkType: hard
@@ -7605,6 +7607,13 @@ __metadata:
languageName: node
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":
version: 1.1.0
resolution: "strict-uri-encode@npm:1.1.0"
@@ -7928,9 +7937,9 @@ __metadata:
languageName: node
linkType: hard
"terser@npm:^5.29.1":
version: 5.29.1
resolution: "terser@npm:5.29.1"
"terser@npm:^5.29.2":
version: 5.29.2
resolution: "terser@npm:5.29.2"
dependencies:
"@jridgewell/source-map": "npm:^0.3.3"
acorn: "npm:^8.8.2"
@@ -7938,7 +7947,7 @@ __metadata:
source-map-support: "npm:~0.5.20"
bin:
terser: bin/terser
checksum: 10/e8c036e7cd7d9e988765272453acdc52a019827e10710cf109c86d6f31248c8d4d8aa3a3deef30f931a2bb75a8ffc731ca947bd126e7d4e6dda157e6fe892ac0
checksum: 10/062df6a8f99ea2635d1b3ce41cfd4180dea6e1c83db9b2cf4b525170b2446d10e069d2877d8dcb59fbf6045870efa17b56462b67045ef2d2b420870f9d144690
languageName: node
linkType: hard
@@ -8020,7 +8029,7 @@ __metadata:
languageName: node
linkType: hard
"tsconfck@npm:^3.0.1":
"tsconfck@npm:^3.0.3":
version: 3.0.3
resolution: "tsconfck@npm:3.0.3"
peerDependencies:
@@ -8329,19 +8338,19 @@ __metadata:
languageName: node
linkType: hard
"vite-tsconfig-paths@npm:^4.3.1":
version: 4.3.1
resolution: "vite-tsconfig-paths@npm:4.3.1"
"vite-tsconfig-paths@npm:^4.3.2":
version: 4.3.2
resolution: "vite-tsconfig-paths@npm:4.3.2"
dependencies:
debug: "npm:^4.1.1"
globrex: "npm:^0.1.2"
tsconfck: "npm:^3.0.1"
tsconfck: "npm:^3.0.3"
peerDependencies:
vite: "*"
peerDependenciesMeta:
vite:
optional: true
checksum: 10/1432f80750f5cbe181c265eb9fc2e9fff8b25a2858f176dc0a02311e3e826333526ee9c16bb0aaaa8555a417ea944d68a2e8225181215cd9502370f913eb3f79
checksum: 10/c12e2087fd01ac8a694850c649b79d5b9798cdba0ef9ab4116f669d8ffa1a9a3195c5a14410d3d9a12d2f08cd35ddd74f03d9c7b13a2d590d002055cdaab45c0
languageName: node
linkType: hard