mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 15:59:52 +03:00
fix: move showing version to settings. fixes JWT resetting after each version change
This commit is contained in:
@@ -1,12 +1,39 @@
|
|||||||
import React, { RefObject, Fragment } from 'react';
|
import React, { RefObject, Fragment } from 'react';
|
||||||
import { Link, withRouter, RouteComponentProps } from 'react-router-dom';
|
import { Link, withRouter, RouteComponentProps } from 'react-router-dom';
|
||||||
|
|
||||||
import { Drawer, AppBar, Toolbar, Avatar, Divider, Button, Box, IconButton } from '@material-ui/core';
|
import {
|
||||||
import { ClickAwayListener, Popper, Hidden, Typography } from '@material-ui/core';
|
Drawer,
|
||||||
import { List, ListItem, ListItemIcon, ListItemText, ListItemAvatar } from '@material-ui/core';
|
AppBar,
|
||||||
|
Toolbar,
|
||||||
|
Avatar,
|
||||||
|
Divider,
|
||||||
|
Button,
|
||||||
|
Box,
|
||||||
|
IconButton
|
||||||
|
} from '@material-ui/core';
|
||||||
|
import {
|
||||||
|
ClickAwayListener,
|
||||||
|
Popper,
|
||||||
|
Hidden,
|
||||||
|
Typography
|
||||||
|
} from '@material-ui/core';
|
||||||
|
import {
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
ListItemAvatar
|
||||||
|
} from '@material-ui/core';
|
||||||
import { Card, CardContent, CardActions } from '@material-ui/core';
|
import { Card, CardContent, CardActions } from '@material-ui/core';
|
||||||
|
|
||||||
import { withStyles, createStyles, Theme, WithTheme, WithStyles, withTheme } from '@material-ui/core/styles';
|
import {
|
||||||
|
withStyles,
|
||||||
|
createStyles,
|
||||||
|
Theme,
|
||||||
|
WithTheme,
|
||||||
|
WithStyles,
|
||||||
|
withTheme
|
||||||
|
} from '@material-ui/core/styles';
|
||||||
|
|
||||||
import SettingsEthernetIcon from '@material-ui/icons/SettingsEthernet';
|
import SettingsEthernetIcon from '@material-ui/icons/SettingsEthernet';
|
||||||
import SettingsIcon from '@material-ui/icons/Settings';
|
import SettingsIcon from '@material-ui/icons/Settings';
|
||||||
@@ -19,76 +46,84 @@ import MenuIcon from '@material-ui/icons/Menu';
|
|||||||
|
|
||||||
import ProjectMenu from '../project/ProjectMenu';
|
import ProjectMenu from '../project/ProjectMenu';
|
||||||
import { PROJECT_NAME } from '../api';
|
import { PROJECT_NAME } from '../api';
|
||||||
import { withAuthenticatedContext, AuthenticatedContextProps } from '../authentication';
|
import {
|
||||||
|
withAuthenticatedContext,
|
||||||
|
AuthenticatedContextProps
|
||||||
|
} from '../authentication';
|
||||||
import { withFeatures, WithFeaturesProps } from '../features/FeaturesContext';
|
import { withFeatures, WithFeaturesProps } from '../features/FeaturesContext';
|
||||||
|
|
||||||
const drawerWidth = 290;
|
const drawerWidth = 290;
|
||||||
|
|
||||||
const styles = (theme: Theme) => createStyles({
|
const styles = (theme: Theme) =>
|
||||||
root: {
|
createStyles({
|
||||||
display: 'flex',
|
root: {
|
||||||
},
|
display: 'flex'
|
||||||
drawer: {
|
|
||||||
[theme.breakpoints.up('md')]: {
|
|
||||||
width: drawerWidth,
|
|
||||||
flexShrink: 0,
|
|
||||||
},
|
},
|
||||||
},
|
drawer: {
|
||||||
title: {
|
[theme.breakpoints.up('md')]: {
|
||||||
flexGrow: 1
|
width: drawerWidth,
|
||||||
},
|
flexShrink: 0
|
||||||
appBar: {
|
}
|
||||||
marginLeft: drawerWidth,
|
|
||||||
[theme.breakpoints.up('md')]: {
|
|
||||||
width: `calc(100% - ${drawerWidth}px)`,
|
|
||||||
},
|
},
|
||||||
},
|
title: {
|
||||||
toolbarImage: {
|
flexGrow: 1
|
||||||
[theme.breakpoints.up('xs')]: {
|
|
||||||
height: 24,
|
|
||||||
marginRight: theme.spacing(2)
|
|
||||||
},
|
},
|
||||||
[theme.breakpoints.up('sm')]: {
|
appBar: {
|
||||||
height: 36,
|
marginLeft: drawerWidth,
|
||||||
marginRight: theme.spacing(3)
|
[theme.breakpoints.up('md')]: {
|
||||||
|
width: `calc(100% - ${drawerWidth}px)`
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
toolbarImage: {
|
||||||
menuButton: {
|
[theme.breakpoints.up('xs')]: {
|
||||||
marginRight: theme.spacing(2),
|
height: 24,
|
||||||
[theme.breakpoints.up('md')]: {
|
marginRight: theme.spacing(2)
|
||||||
display: 'none',
|
},
|
||||||
|
[theme.breakpoints.up('sm')]: {
|
||||||
|
height: 36,
|
||||||
|
marginRight: theme.spacing(3)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
menuButton: {
|
||||||
toolbar: theme.mixins.toolbar,
|
marginRight: theme.spacing(2),
|
||||||
drawerPaper: {
|
[theme.breakpoints.up('md')]: {
|
||||||
width: drawerWidth,
|
display: 'none'
|
||||||
},
|
}
|
||||||
content: {
|
},
|
||||||
flexGrow: 1
|
toolbar: theme.mixins.toolbar,
|
||||||
},
|
drawerPaper: {
|
||||||
authMenu: {
|
width: drawerWidth
|
||||||
zIndex: theme.zIndex.tooltip,
|
},
|
||||||
maxWidth: 400,
|
content: {
|
||||||
},
|
flexGrow: 1
|
||||||
authMenuActions: {
|
},
|
||||||
padding: theme.spacing(2),
|
authMenu: {
|
||||||
"& > * + *": {
|
zIndex: theme.zIndex.tooltip,
|
||||||
marginLeft: theme.spacing(2),
|
maxWidth: 400
|
||||||
|
},
|
||||||
|
authMenuActions: {
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
'& > * + *': {
|
||||||
|
marginLeft: theme.spacing(2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
interface MenuAppBarState {
|
interface MenuAppBarState {
|
||||||
mobileOpen: boolean;
|
mobileOpen: boolean;
|
||||||
authMenuOpen: boolean;
|
authMenuOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MenuAppBarProps extends WithFeaturesProps, AuthenticatedContextProps, WithTheme, WithStyles<typeof styles>, RouteComponentProps {
|
interface MenuAppBarProps
|
||||||
|
extends WithFeaturesProps,
|
||||||
|
AuthenticatedContextProps,
|
||||||
|
WithTheme,
|
||||||
|
WithStyles<typeof styles>,
|
||||||
|
RouteComponentProps {
|
||||||
sectionTitle: string;
|
sectionTitle: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MenuAppBar extends React.Component<MenuAppBarProps, MenuAppBarState> {
|
class MenuAppBar extends React.Component<MenuAppBarProps, MenuAppBarState> {
|
||||||
|
|
||||||
constructor(props: MenuAppBarProps) {
|
constructor(props: MenuAppBarProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -101,38 +136,48 @@ class MenuAppBar extends React.Component<MenuAppBarProps, MenuAppBarState> {
|
|||||||
|
|
||||||
handleToggle = () => {
|
handleToggle = () => {
|
||||||
this.setState({ authMenuOpen: !this.state.authMenuOpen });
|
this.setState({ authMenuOpen: !this.state.authMenuOpen });
|
||||||
}
|
};
|
||||||
|
|
||||||
handleClose = (event: React.MouseEvent<Document>) => {
|
handleClose = (event: React.MouseEvent<Document>) => {
|
||||||
if (this.anchorRef.current && this.anchorRef.current.contains(event.currentTarget)) {
|
if (
|
||||||
|
this.anchorRef.current &&
|
||||||
|
this.anchorRef.current.contains(event.currentTarget)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState({ authMenuOpen: false });
|
this.setState({ authMenuOpen: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
handleDrawerToggle = () => {
|
handleDrawerToggle = () => {
|
||||||
this.setState({ mobileOpen: !this.state.mobileOpen });
|
this.setState({ mobileOpen: !this.state.mobileOpen });
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { classes, theme, children, sectionTitle, authenticatedContext, features } = this.props;
|
const {
|
||||||
|
classes,
|
||||||
|
theme,
|
||||||
|
children,
|
||||||
|
sectionTitle,
|
||||||
|
authenticatedContext,
|
||||||
|
features
|
||||||
|
} = this.props;
|
||||||
const { mobileOpen, authMenuOpen } = this.state;
|
const { mobileOpen, authMenuOpen } = this.state;
|
||||||
const path = this.props.match.url;
|
const path = this.props.match.url;
|
||||||
const drawer = (
|
const drawer = (
|
||||||
<div>
|
<div>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<Box display="flex">
|
<Box display="flex">
|
||||||
<img src="/app/icon.png" className={classes.toolbarImage} alt={PROJECT_NAME} />
|
<img
|
||||||
|
src="/app/icon.png"
|
||||||
|
className={classes.toolbarImage}
|
||||||
|
alt={PROJECT_NAME}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Typography variant="h6" color="textPrimary">
|
<Typography variant="h6" color="textPrimary">
|
||||||
{PROJECT_NAME}
|
{PROJECT_NAME}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography align="right" variant="caption" color="textPrimary">
|
|
||||||
v{authenticatedContext.me.version}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Divider absolute />
|
<Divider absolute />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
||||||
@@ -144,20 +189,35 @@ class MenuAppBar extends React.Component<MenuAppBarProps, MenuAppBarState> {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<List>
|
<List>
|
||||||
<ListItem to='/network/' selected={path.startsWith('/network/')} button component={Link}>
|
<ListItem
|
||||||
|
to="/network/"
|
||||||
|
selected={path.startsWith('/network/')}
|
||||||
|
button
|
||||||
|
component={Link}
|
||||||
|
>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<SettingsEthernetIcon />
|
<SettingsEthernetIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary="Network Connection" />
|
<ListItemText primary="Network Connection" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem to='/ap/' selected={path.startsWith('/ap/')} button component={Link}>
|
<ListItem
|
||||||
|
to="/ap/"
|
||||||
|
selected={path.startsWith('/ap/')}
|
||||||
|
button
|
||||||
|
component={Link}
|
||||||
|
>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<SettingsInputAntennaIcon />
|
<SettingsInputAntennaIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary="Access Point" />
|
<ListItemText primary="Access Point" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{features.ntp && (
|
{features.ntp && (
|
||||||
<ListItem to='/ntp/' selected={path.startsWith('/ntp/')} button component={Link}>
|
<ListItem
|
||||||
|
to="/ntp/"
|
||||||
|
selected={path.startsWith('/ntp/')}
|
||||||
|
button
|
||||||
|
component={Link}
|
||||||
|
>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<AccessTimeIcon />
|
<AccessTimeIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
@@ -165,7 +225,12 @@ class MenuAppBar extends React.Component<MenuAppBarProps, MenuAppBarState> {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
{features.mqtt && (
|
{features.mqtt && (
|
||||||
<ListItem to='/mqtt/' selected={path.startsWith('/mqtt/')} button component={Link}>
|
<ListItem
|
||||||
|
to="/mqtt/"
|
||||||
|
selected={path.startsWith('/mqtt/')}
|
||||||
|
button
|
||||||
|
component={Link}
|
||||||
|
>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<DeviceHubIcon />
|
<DeviceHubIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
@@ -173,14 +238,25 @@ class MenuAppBar extends React.Component<MenuAppBarProps, MenuAppBarState> {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
{features.security && (
|
{features.security && (
|
||||||
<ListItem to='/security/' selected={path.startsWith('/security/')} button component={Link} disabled={!authenticatedContext.me.admin}>
|
<ListItem
|
||||||
|
to="/security/"
|
||||||
|
selected={path.startsWith('/security/')}
|
||||||
|
button
|
||||||
|
component={Link}
|
||||||
|
disabled={!authenticatedContext.me.admin}
|
||||||
|
>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<LockIcon />
|
<LockIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary="Security" />
|
<ListItemText primary="Security" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
<ListItem to='/system/' selected={path.startsWith('/system/')} button component={Link} >
|
<ListItem
|
||||||
|
to="/system/"
|
||||||
|
selected={path.startsWith('/system/')}
|
||||||
|
button
|
||||||
|
component={Link}
|
||||||
|
>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
@@ -201,7 +277,12 @@ class MenuAppBar extends React.Component<MenuAppBarProps, MenuAppBarState> {
|
|||||||
>
|
>
|
||||||
<AccountCircleIcon />
|
<AccountCircleIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Popper open={authMenuOpen} anchorEl={this.anchorRef.current} transition className={classes.authMenu}>
|
<Popper
|
||||||
|
open={authMenuOpen}
|
||||||
|
anchorEl={this.anchorRef.current}
|
||||||
|
transition
|
||||||
|
className={classes.authMenu}
|
||||||
|
>
|
||||||
<ClickAwayListener onClickAway={this.handleClose}>
|
<ClickAwayListener onClickAway={this.handleClose}>
|
||||||
<Card id="menu-list-grow">
|
<Card id="menu-list-grow">
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@@ -212,13 +293,27 @@ class MenuAppBar extends React.Component<MenuAppBarProps, MenuAppBarState> {
|
|||||||
<AccountCircleIcon />
|
<AccountCircleIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText primary={"Signed in as: " + authenticatedContext.me.username} secondary={authenticatedContext.me.admin ? "Admin User" : undefined} />
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
'Signed in as: ' + authenticatedContext.me.username
|
||||||
|
}
|
||||||
|
secondary={
|
||||||
|
authenticatedContext.me.admin ? 'Admin User' : undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<Divider />
|
<Divider />
|
||||||
<CardActions className={classes.authMenuActions}>
|
<CardActions className={classes.authMenuActions}>
|
||||||
<Button variant="contained" fullWidth color="primary" onClick={authenticatedContext.signOut}>Sign Out</Button>
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
color="primary"
|
||||||
|
onClick={authenticatedContext.signOut}
|
||||||
|
>
|
||||||
|
Sign Out
|
||||||
|
</Button>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</Card>
|
</Card>
|
||||||
</ClickAwayListener>
|
</ClickAwayListener>
|
||||||
@@ -239,7 +334,12 @@ class MenuAppBar extends React.Component<MenuAppBarProps, MenuAppBarState> {
|
|||||||
>
|
>
|
||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography variant="h6" color="inherit" noWrap className={classes.title}>
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
color="inherit"
|
||||||
|
noWrap
|
||||||
|
className={classes.title}
|
||||||
|
>
|
||||||
{sectionTitle}
|
{sectionTitle}
|
||||||
</Typography>
|
</Typography>
|
||||||
{features.security && userMenu}
|
{features.security && userMenu}
|
||||||
@@ -253,10 +353,10 @@ class MenuAppBar extends React.Component<MenuAppBarProps, MenuAppBarState> {
|
|||||||
open={mobileOpen}
|
open={mobileOpen}
|
||||||
onClose={this.handleDrawerToggle}
|
onClose={this.handleDrawerToggle}
|
||||||
classes={{
|
classes={{
|
||||||
paper: classes.drawerPaper,
|
paper: classes.drawerPaper
|
||||||
}}
|
}}
|
||||||
ModalProps={{
|
ModalProps={{
|
||||||
keepMounted: true,
|
keepMounted: true
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{drawer}
|
{drawer}
|
||||||
@@ -265,7 +365,7 @@ class MenuAppBar extends React.Component<MenuAppBarProps, MenuAppBarState> {
|
|||||||
<Hidden smDown implementation="css">
|
<Hidden smDown implementation="css">
|
||||||
<Drawer
|
<Drawer
|
||||||
classes={{
|
classes={{
|
||||||
paper: classes.drawerPaper,
|
paper: classes.drawerPaper
|
||||||
}}
|
}}
|
||||||
variant="permanent"
|
variant="permanent"
|
||||||
open
|
open
|
||||||
@@ -285,10 +385,6 @@ class MenuAppBar extends React.Component<MenuAppBarProps, MenuAppBarState> {
|
|||||||
|
|
||||||
export default withRouter(
|
export default withRouter(
|
||||||
withTheme(
|
withTheme(
|
||||||
withFeatures(
|
withFeatures(withAuthenticatedContext(withStyles(styles)(MenuAppBar)))
|
||||||
withAuthenticatedContext(
|
|
||||||
withStyles(styles)(MenuAppBar)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,21 @@
|
|||||||
import React, { Component, Fragment } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
|
|
||||||
import { Avatar, Button, Divider, Dialog, DialogTitle, DialogContent, DialogActions, Box } from '@material-ui/core';
|
import {
|
||||||
import { List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core';
|
Avatar,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
Box
|
||||||
|
} from '@material-ui/core';
|
||||||
|
import {
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemAvatar,
|
||||||
|
ListItemText
|
||||||
|
} from '@material-ui/core';
|
||||||
|
|
||||||
import DevicesIcon from '@material-ui/icons/Devices';
|
import DevicesIcon from '@material-ui/icons/Devices';
|
||||||
import MemoryIcon from '@material-ui/icons/Memory';
|
import MemoryIcon from '@material-ui/icons/Memory';
|
||||||
@@ -11,9 +25,14 @@ import AppsIcon from '@material-ui/icons/Apps';
|
|||||||
import PowerSettingsNewIcon from '@material-ui/icons/PowerSettingsNew';
|
import PowerSettingsNewIcon from '@material-ui/icons/PowerSettingsNew';
|
||||||
import RefreshIcon from '@material-ui/icons/Refresh';
|
import RefreshIcon from '@material-ui/icons/Refresh';
|
||||||
import SettingsBackupRestoreIcon from '@material-ui/icons/SettingsBackupRestore';
|
import SettingsBackupRestoreIcon from '@material-ui/icons/SettingsBackupRestore';
|
||||||
import TimerIcon from "@material-ui/icons/Timer";
|
import TimerIcon from '@material-ui/icons/Timer';
|
||||||
|
import BuildIcon from '@material-ui/icons/Build';
|
||||||
|
|
||||||
import { redirectingAuthorizedFetch, AuthenticatedContextProps, withAuthenticatedContext } from '../authentication';
|
import {
|
||||||
|
redirectingAuthorizedFetch,
|
||||||
|
AuthenticatedContextProps,
|
||||||
|
withAuthenticatedContext
|
||||||
|
} from '../authentication';
|
||||||
import { RestFormProps, FormButton, ErrorButton } from '../components';
|
import { RestFormProps, FormButton, ErrorButton } from '../components';
|
||||||
import { FACTORY_RESET_ENDPOINT, RESTART_ENDPOINT } from '../api';
|
import { FACTORY_RESET_ENDPOINT, RESTART_ENDPOINT } from '../api';
|
||||||
|
|
||||||
@@ -25,31 +44,48 @@ interface SystemStatusFormState {
|
|||||||
processing: boolean;
|
processing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SystemStatusFormProps = AuthenticatedContextProps & RestFormProps<SystemStatus>;
|
type SystemStatusFormProps = AuthenticatedContextProps &
|
||||||
|
RestFormProps<SystemStatus>;
|
||||||
|
|
||||||
function formatNumber(num: number) {
|
function formatNumber(num: number) {
|
||||||
return new Intl.NumberFormat().format(num);
|
return new Intl.NumberFormat().format(num);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusFormState> {
|
class SystemStatusForm extends Component<
|
||||||
|
SystemStatusFormProps,
|
||||||
|
SystemStatusFormState
|
||||||
|
> {
|
||||||
state: SystemStatusFormState = {
|
state: SystemStatusFormState = {
|
||||||
confirmRestart: false,
|
confirmRestart: false,
|
||||||
confirmFactoryReset: false,
|
confirmFactoryReset: false,
|
||||||
processing: false
|
processing: false
|
||||||
}
|
};
|
||||||
|
|
||||||
createListItems() {
|
createListItems() {
|
||||||
const { data } = this.props
|
const { data } = this.props;
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<ListItem >
|
<ListItem>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar>
|
||||||
|
<BuildIcon />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary="EMS-ESP Version"
|
||||||
|
secondary={'v' + data.emsesp_version}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar>
|
<Avatar>
|
||||||
<DevicesIcon />
|
<DevicesIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText primary="Device (Platform / SDK)" secondary={data.esp_platform + ' / ' + data.sdk_version} />
|
<ListItemText
|
||||||
|
primary="Device (Platform / SDK)"
|
||||||
|
secondary={data.esp_platform + ' / ' + data.sdk_version}
|
||||||
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider variant="inset" component="li" />
|
<Divider variant="inset" component="li" />
|
||||||
<ListItem>
|
<ListItem>
|
||||||
@@ -61,45 +97,76 @@ class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusForm
|
|||||||
<ListItemText primary="System Uptime" secondary={data.uptime} />
|
<ListItemText primary="System Uptime" secondary={data.uptime} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider variant="inset" component="li" />
|
<Divider variant="inset" component="li" />
|
||||||
<ListItem >
|
<ListItem>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar>
|
<Avatar>
|
||||||
<ShowChartIcon />
|
<ShowChartIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText primary="CPU Frequency" secondary={data.cpu_freq_mhz + ' MHz'} />
|
<ListItemText
|
||||||
|
primary="CPU Frequency"
|
||||||
|
secondary={data.cpu_freq_mhz + ' MHz'}
|
||||||
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider variant="inset" component="li" />
|
<Divider variant="inset" component="li" />
|
||||||
<ListItem >
|
<ListItem>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar>
|
<Avatar>
|
||||||
<MemoryIcon />
|
<MemoryIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText primary="Heap (Free / Max Alloc)" secondary={formatNumber(data.free_heap) + ' / ' + formatNumber(data.max_alloc_heap) + ' bytes ' + (data.esp_platform === EspPlatform.ESP8266 ? '(' + data.heap_fragmentation + '% fragmentation)' : '')} />
|
<ListItemText
|
||||||
|
primary="Heap (Free / Max Alloc)"
|
||||||
|
secondary={
|
||||||
|
formatNumber(data.free_heap) +
|
||||||
|
' / ' +
|
||||||
|
formatNumber(data.max_alloc_heap) +
|
||||||
|
' bytes ' +
|
||||||
|
(data.esp_platform === EspPlatform.ESP8266
|
||||||
|
? '(' + data.heap_fragmentation + '% fragmentation)'
|
||||||
|
: '')
|
||||||
|
}
|
||||||
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{
|
{data.esp_platform === EspPlatform.ESP32 && data.psram_size > 0 && (
|
||||||
(data.esp_platform === EspPlatform.ESP32 && data.psram_size > 0) && (
|
<Fragment>
|
||||||
<Fragment>
|
<Divider variant="inset" component="li" />
|
||||||
<Divider variant="inset" component="li" />
|
<ListItem>
|
||||||
<ListItem >
|
<ListItemAvatar>
|
||||||
<ListItemAvatar>
|
<Avatar>
|
||||||
<Avatar>
|
<AppsIcon />
|
||||||
<AppsIcon />
|
</Avatar>
|
||||||
</Avatar>
|
</ListItemAvatar>
|
||||||
</ListItemAvatar>
|
<ListItemText
|
||||||
<ListItemText primary="PSRAM (Size / Free)" secondary={formatNumber(data.psram_size) + ' / ' + formatNumber(data.free_psram) + ' bytes'} />
|
primary="PSRAM (Size / Free)"
|
||||||
</ListItem>
|
secondary={
|
||||||
</Fragment>)
|
formatNumber(data.psram_size) +
|
||||||
}
|
' / ' +
|
||||||
|
formatNumber(data.free_psram) +
|
||||||
|
' bytes'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
<Divider variant="inset" component="li" />
|
<Divider variant="inset" component="li" />
|
||||||
<ListItem >
|
<ListItem>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar>
|
<Avatar>
|
||||||
<FolderIcon />
|
<FolderIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText primary="File System (Used / Total)" secondary={formatNumber(data.fs_used) + ' / ' + formatNumber(data.fs_total) + ' bytes (' + formatNumber(data.fs_total - data.fs_used) + '\xa0bytes free)'} />
|
<ListItemText
|
||||||
|
primary="File System (Used / Total)"
|
||||||
|
secondary={
|
||||||
|
formatNumber(data.fs_used) +
|
||||||
|
' / ' +
|
||||||
|
formatNumber(data.fs_total) +
|
||||||
|
' bytes (' +
|
||||||
|
formatNumber(data.fs_total - data.fs_used) +
|
||||||
|
'\xa0bytes free)'
|
||||||
|
}
|
||||||
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider variant="inset" component="li" />
|
<Divider variant="inset" component="li" />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
@@ -119,41 +186,57 @@ class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusForm
|
|||||||
Are you sure you want to restart the device?
|
Are you sure you want to restart the device?
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button variant="contained" onClick={this.onRestartRejected} color="secondary">
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={this.onRestartRejected}
|
||||||
|
color="secondary"
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" onClick={this.onRestartConfirmed} disabled={this.state.processing} color="primary" autoFocus>
|
<Button
|
||||||
|
startIcon={<PowerSettingsNewIcon />}
|
||||||
|
variant="contained"
|
||||||
|
onClick={this.onRestartConfirmed}
|
||||||
|
disabled={this.state.processing}
|
||||||
|
color="primary"
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
Restart
|
Restart
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onRestart = () => {
|
onRestart = () => {
|
||||||
this.setState({ confirmRestart: true });
|
this.setState({ confirmRestart: true });
|
||||||
}
|
};
|
||||||
|
|
||||||
onRestartRejected = () => {
|
onRestartRejected = () => {
|
||||||
this.setState({ confirmRestart: false });
|
this.setState({ confirmRestart: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
onRestartConfirmed = () => {
|
onRestartConfirmed = () => {
|
||||||
this.setState({ processing: true });
|
this.setState({ processing: true });
|
||||||
redirectingAuthorizedFetch(RESTART_ENDPOINT, { method: 'POST' })
|
redirectingAuthorizedFetch(RESTART_ENDPOINT, { method: 'POST' })
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
this.props.enqueueSnackbar("Device is restarting", { variant: 'info' });
|
this.props.enqueueSnackbar('Device is restarting', {
|
||||||
|
variant: 'info'
|
||||||
|
});
|
||||||
this.setState({ processing: false, confirmRestart: false });
|
this.setState({ processing: false, confirmRestart: false });
|
||||||
} else {
|
} else {
|
||||||
throw Error("Invalid status code: " + response.status);
|
throw Error('Invalid status code: ' + response.status);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
this.props.enqueueSnackbar(error.message || "Problem restarting device", { variant: 'error' });
|
this.props.enqueueSnackbar(
|
||||||
|
error.message || 'Problem restarting device',
|
||||||
|
{ variant: 'error' }
|
||||||
|
);
|
||||||
this.setState({ processing: false, confirmRestart: false });
|
this.setState({ processing: false, confirmRestart: false });
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
renderFactoryResetDialog() {
|
renderFactoryResetDialog() {
|
||||||
return (
|
return (
|
||||||
@@ -168,72 +251,98 @@ class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusForm
|
|||||||
Are you sure you want to reset the device to its factory defaults?
|
Are you sure you want to reset the device to its factory defaults?
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button variant="contained" onClick={this.onFactoryResetRejected} color="secondary">
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={this.onFactoryResetRejected}
|
||||||
|
color="secondary"
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<ErrorButton startIcon={<SettingsBackupRestoreIcon />} variant="contained" onClick={this.onFactoryResetConfirmed} disabled={this.state.processing} autoFocus>
|
<ErrorButton
|
||||||
|
startIcon={<SettingsBackupRestoreIcon />}
|
||||||
|
variant="contained"
|
||||||
|
onClick={this.onFactoryResetConfirmed}
|
||||||
|
disabled={this.state.processing}
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
Factory Reset
|
Factory Reset
|
||||||
</ErrorButton>
|
</ErrorButton>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFactoryReset = () => {
|
onFactoryReset = () => {
|
||||||
this.setState({ confirmFactoryReset: true });
|
this.setState({ confirmFactoryReset: true });
|
||||||
}
|
};
|
||||||
|
|
||||||
onFactoryResetRejected = () => {
|
onFactoryResetRejected = () => {
|
||||||
this.setState({ confirmFactoryReset: false });
|
this.setState({ confirmFactoryReset: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
onFactoryResetConfirmed = () => {
|
onFactoryResetConfirmed = () => {
|
||||||
this.setState({ processing: true });
|
this.setState({ processing: true });
|
||||||
redirectingAuthorizedFetch(FACTORY_RESET_ENDPOINT, { method: 'POST' })
|
redirectingAuthorizedFetch(FACTORY_RESET_ENDPOINT, { method: 'POST' })
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
this.props.enqueueSnackbar("Factory reset in progress.", { variant: 'error' });
|
this.props.enqueueSnackbar('Factory reset in progress.', {
|
||||||
|
variant: 'error'
|
||||||
|
});
|
||||||
this.setState({ processing: false, confirmFactoryReset: false });
|
this.setState({ processing: false, confirmFactoryReset: false });
|
||||||
} else {
|
} else {
|
||||||
throw Error("Invalid status code: " + response.status);
|
throw Error('Invalid status code: ' + response.status);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
this.props.enqueueSnackbar(error.message || "Problem factory resetting device", { variant: 'error' });
|
this.props.enqueueSnackbar(
|
||||||
|
error.message || 'Problem factory resetting device',
|
||||||
|
{ variant: 'error' }
|
||||||
|
);
|
||||||
this.setState({ processing: false, confirmRestart: false });
|
this.setState({ processing: false, confirmRestart: false });
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const me = this.props.authenticatedContext.me;
|
const me = this.props.authenticatedContext.me;
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<List>
|
<List>{this.createListItems()}</List>
|
||||||
{this.createListItems()}
|
|
||||||
</List>
|
|
||||||
<Box display="flex" flexWrap="wrap">
|
<Box display="flex" flexWrap="wrap">
|
||||||
<Box flexGrow={1} padding={1}>
|
<Box flexGrow={1} padding={1}>
|
||||||
<FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData}>
|
<FormButton
|
||||||
|
startIcon={<RefreshIcon />}
|
||||||
|
variant="contained"
|
||||||
|
color="secondary"
|
||||||
|
onClick={this.props.loadData}
|
||||||
|
>
|
||||||
Refresh
|
Refresh
|
||||||
</FormButton>
|
</FormButton>
|
||||||
</Box>
|
</Box>
|
||||||
{me.admin &&
|
{me.admin && (
|
||||||
<Box flexWrap="none" padding={1} whiteSpace="nowrap">
|
<Box flexWrap="none" padding={1} whiteSpace="nowrap">
|
||||||
<FormButton startIcon={<PowerSettingsNewIcon />} variant="contained" color="primary" onClick={this.onRestart}>
|
<FormButton
|
||||||
|
startIcon={<PowerSettingsNewIcon />}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={this.onRestart}
|
||||||
|
>
|
||||||
Restart
|
Restart
|
||||||
</FormButton>
|
</FormButton>
|
||||||
<ErrorButton startIcon={<SettingsBackupRestoreIcon />} variant="contained" onClick={this.onFactoryReset}>
|
<ErrorButton
|
||||||
|
startIcon={<SettingsBackupRestoreIcon />}
|
||||||
|
variant="contained"
|
||||||
|
onClick={this.onFactoryReset}
|
||||||
|
>
|
||||||
Factory reset
|
Factory reset
|
||||||
</ErrorButton>
|
</ErrorButton>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{this.renderRestartDialog()}
|
{this.renderRestartDialog()}
|
||||||
{this.renderFactoryResetDialog()}
|
{this.renderFactoryResetDialog()}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withAuthenticatedContext(SystemStatusForm);
|
export default withAuthenticatedContext(SystemStatusForm);
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ Authentication SecuritySettingsService::authenticate(const String & username, co
|
|||||||
inline void populateJWTPayload(JsonObject & payload, User * user) {
|
inline void populateJWTPayload(JsonObject & payload, User * user) {
|
||||||
payload["username"] = user->username;
|
payload["username"] = user->username;
|
||||||
payload["admin"] = user->admin;
|
payload["admin"] = user->admin;
|
||||||
payload["version"] = EMSESP_APP_VERSION; // proddy added
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean SecuritySettingsService::validatePayload(JsonObject & parsedPayload, User * user) {
|
boolean SecuritySettingsService::validatePayload(JsonObject & parsedPayload, User * user) {
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
#include <SystemStatus.h>
|
#include <SystemStatus.h>
|
||||||
|
|
||||||
|
#include "../../src/emsesp_stub.hpp" // proddy added
|
||||||
|
|
||||||
using namespace std::placeholders; // for `_1` etc
|
using namespace std::placeholders; // for `_1` etc
|
||||||
|
|
||||||
SystemStatus::SystemStatus(AsyncWebServer * server, SecurityManager * securityManager) {
|
SystemStatus::SystemStatus(AsyncWebServer * server, SecurityManager * securityManager) {
|
||||||
server->on(SYSTEM_STATUS_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&SystemStatus::systemStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
server->on(SYSTEM_STATUS_SERVICE_PATH,
|
||||||
|
HTTP_GET,
|
||||||
|
securityManager->wrapRequest(std::bind(&SystemStatus::systemStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SystemStatus::systemStatus(AsyncWebServerRequest * request) {
|
void SystemStatus::systemStatus(AsyncWebServerRequest * request) {
|
||||||
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_ESP_STATUS_SIZE);
|
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_ESP_STATUS_SIZE);
|
||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
|
root["emsesp_version"] = EMSESP_APP_VERSION;
|
||||||
root["esp_platform"] = "ESP32";
|
root["esp_platform"] = "ESP32";
|
||||||
root["max_alloc_heap"] = ESP.getMaxAllocHeap();
|
root["max_alloc_heap"] = ESP.getMaxAllocHeap();
|
||||||
root["psram_size"] = ESP.getPsramSize();
|
root["psram_size"] = ESP.getPsramSize();
|
||||||
|
|||||||
@@ -10,8 +10,6 @@
|
|||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include <SecurityManager.h>
|
#include <SecurityManager.h>
|
||||||
|
|
||||||
#include <uuid/log.h> // proddy added
|
|
||||||
|
|
||||||
#define MAX_ESP_STATUS_SIZE 1024
|
#define MAX_ESP_STATUS_SIZE 1024
|
||||||
#define SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus"
|
#define SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus"
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ Authentication SecuritySettingsService::authenticate(const String& username, con
|
|||||||
inline void populateJWTPayload(JsonObject& payload, User* user) {
|
inline void populateJWTPayload(JsonObject& payload, User* user) {
|
||||||
payload["username"] = user->username;
|
payload["username"] = user->username;
|
||||||
payload["admin"] = user->admin;
|
payload["admin"] = user->admin;
|
||||||
payload["version"] = EMSESP_APP_VERSION; // proddy added
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean SecuritySettingsService::validatePayload(JsonObject& parsedPayload, User* user) {
|
boolean SecuritySettingsService::validatePayload(JsonObject& parsedPayload, User* user) {
|
||||||
|
|||||||
Reference in New Issue
Block a user