status screen updates

This commit is contained in:
proddy
2024-03-19 23:25:31 +01:00
parent 217b424320
commit 863bc04c21
42 changed files with 773 additions and 583 deletions

View File

@@ -4,6 +4,7 @@ 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 MemoryIcon from '@mui/icons-material/Memory';
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
@@ -24,7 +25,8 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Box
Box,
Divider
} from '@mui/material';
import { useRequest } from 'alova';
import { useState, type FC } from 'react';
@@ -294,6 +296,26 @@ const Settings: FC = () => {
</ListItemButton>
</ListItem>
<Divider />
<ListItem
disablePadding
secondaryAction={
<ListItemIcon style={{ justifyContent: 'right', color: 'lightblue', verticalAlign: 'middle' }}>
<NavigateNextIcon />
</ListItemIcon>
}
>
<ListItemButton component={Link} to="espsystemstatus">
<ListItemAvatar>
<Avatar sx={{ color: 'white' }}>
<MemoryIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.STATUS_OF('ESP32')} secondary="ESP32 Information" />
</ListItemButton>
</ListItem>
<ListItem
disablePadding
secondaryAction={
@@ -312,8 +334,10 @@ const Settings: FC = () => {
</ListItemButton>
</ListItem>
</List>
{renderRestartDialog()}
{renderFactoryResetDialog()}
<Box display="flex" flexWrap="wrap">
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
<ButtonRow>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -56,7 +56,7 @@ const WiFiNetworkScanner: FC = () => {
};
return (
<SectionContent title={LL.NETWORK_SCANNER()}>
<SectionContent>
{renderNetworkScanner()}
<ButtonRow>
<Button

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
import AppsIcon from '@mui/icons-material/Apps';
import BuildIcon from '@mui/icons-material/Build';
import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard';
import DevicesIcon from '@mui/icons-material/Devices';
import FolderIcon from '@mui/icons-material/Folder';
@@ -7,24 +6,25 @@ import MemoryIcon from '@mui/icons-material/Memory';
import RefreshIcon from '@mui/icons-material/Refresh';
import SdCardAlertIcon from '@mui/icons-material/SdCardAlert';
import SdStorageIcon from '@mui/icons-material/SdStorage';
import TimerIcon from '@mui/icons-material/Timer';
import { Avatar, Box, Button, Divider, List, ListItem, ListItemAvatar, ListItemText } from '@mui/material';
import { useRequest } from 'alova';
import type { FC } from 'react';
import * as SystemApi from 'api/system';
import { ButtonRow, FormLoader, SectionContent } from 'components';
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
function formatNumber(num: number) {
return new Intl.NumberFormat().format(num);
}
const SystemStatusForm: FC = () => {
const ESPSystemStatus: FC = () => {
const { LL } = useI18nContext();
const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus, { force: true });
useLayoutTitle(LL.STATUS_OF('ESP32'));
const { data: data, send: loadData, error } = useRequest(SystemApi.readESPSystemStatus, { force: true });
const content = () => {
if (!data) {
@@ -34,24 +34,6 @@ const SystemStatusForm: FC = () => {
return (
<>
<List>
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#5f9a5f', color: 'white' }}>
<BuildIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.EMS_ESP_VER()} secondary={data.emsesp_version} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#5f9a5f', color: 'white' }}>
<TimerIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.UPTIME()} secondary={data.uptime} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#5f9a5f', color: 'white' }}>
@@ -151,7 +133,6 @@ const SystemStatusForm: FC = () => {
secondary={formatNumber(data.fs_used) + ' KB / ' + formatNumber(data.fs_free) + ' KB'}
/>
</ListItem>
<Divider variant="inset" component="li" />
</List>
<Box display="flex" flexWrap="wrap">
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
@@ -166,7 +147,7 @@ const SystemStatusForm: FC = () => {
);
};
return <SectionContent title={LL.STATUS_OF(LL.SYSTEM(1))}>{content()}</SectionContent>;
return <SectionContent>{content()}</SectionContent>;
};
export default SystemStatusForm;
export default ESPSystemStatus;

View File

@@ -1,13 +1,13 @@
import { Tab } from '@mui/material';
import { Navigate, Routes, Route } from 'react-router-dom';
import SystemLog from './SystemLog';
import SystemStatusForm from './SystemStatusForm';
import SystemStatus from './SystemStatus';
import type { FC } from 'react';
import { useRouterTab, RouterTabs, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import EMSStatus from 'project/EMSStatus';
import Activity from 'project/Activity';
const Status: FC = () => {
const { LL } = useI18nContext();
@@ -20,12 +20,12 @@ const Status: FC = () => {
<>
<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))} />
<Tab value="activity" label={LL.ACTIVITY()} />
<Tab value="log" label={LL.LOG_OF('')} />
</RouterTabs>
<Routes>
<Route path="status" element={<SystemStatusForm />} />
<Route path="emsesp-status" element={<EMSStatus />} />
<Route path="status" element={<SystemStatus />} />
<Route path="activity" element={<Activity />} />
<Route path="log" element={<SystemLog />} />
<Route path="*" element={<Navigate replace to="status" />} />
</Routes>

View File

@@ -232,7 +232,7 @@ const SystemLog: FC = () => {
};
return (
<SectionContent title={LL.LOG_OF(LL.SYSTEM(2))} id="log-window">
<SectionContent id="log-window">
{blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()}
</SectionContent>

View File

@@ -0,0 +1,212 @@
import BuildIcon from '@mui/icons-material/Build';
import CancelIcon from '@mui/icons-material/Cancel';
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import DirectionsBusIcon from '@mui/icons-material/DirectionsBus';
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
import RefreshIcon from '@mui/icons-material/Refresh';
import TimerIcon from '@mui/icons-material/Timer';
import {
Avatar,
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Divider,
List,
ListItem,
ListItemAvatar,
ListItemText,
useTheme
} from '@mui/material';
import { useRequest } from 'alova';
import { useContext, type FC, useState } from 'react';
import { toast } from 'react-toastify';
import { dialogStyle } from 'CustomTheme';
import * as SystemApi from 'api/system';
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from 'project/api';
import { busConnectionStatus } from 'project/types';
const SystemStatus: FC = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.STATUS_OF(''));
const { me } = useContext(AuthenticatedContext);
const [confirmScan, setConfirmScan] = useState<boolean>(false);
const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus, { force: true });
const { send: scanDevices } = useRequest(EMSESP.scanDevices, {
immediate: false
});
const theme = useTheme();
const formatDurationSec = (duration_sec: number) => {
const days = Math.trunc((duration_sec * 1000) / 86400000);
const hours = Math.trunc((duration_sec * 1000) / 3600000) % 24;
const minutes = Math.trunc((duration_sec * 1000) / 60000) % 60;
const seconds = Math.trunc((duration_sec * 1000) / 1000) % 60;
let formatted = '';
if (days) {
formatted += LL.NUM_DAYS({ num: days }) + ' ';
}
if (hours) {
formatted += LL.NUM_HOURS({ num: hours }) + ' ';
}
if (minutes) {
formatted += LL.NUM_MINUTES({ num: minutes }) + ' ';
}
formatted += LL.NUM_SECONDS({ num: seconds });
return formatted;
};
const busStatus = () => {
if (data) {
switch (data.status) {
case busConnectionStatus.BUS_STATUS_CONNECTED:
return LL.CONNECTED(0);
case busConnectionStatus.BUS_STATUS_TX_ERRORS:
return LL.TX_ISSUES();
case busConnectionStatus.BUS_STATUS_OFFLINE:
return LL.DISCONNECTED();
}
}
return 'Unknown';
};
// export const isConnected = ({ status }: Status) => status !== busConnectionStatus.BUS_STATUS_OFFLINE;
const busStatusHighlight = () => {
switch (data.status) {
case busConnectionStatus.BUS_STATUS_TX_ERRORS:
return theme.palette.warning.main;
case busConnectionStatus.BUS_STATUS_CONNECTED:
return theme.palette.success.main;
case busConnectionStatus.BUS_STATUS_OFFLINE:
return theme.palette.error.main;
default:
return theme.palette.warning.main;
}
};
const scan = async () => {
await scanDevices()
.then(() => {
toast.info(LL.SCANNING() + '...');
})
.catch((err) => {
toast.error(err.message);
});
setConfirmScan(false);
};
const renderScanDialog = () => (
<Dialog sx={dialogStyle} open={confirmScan} onClose={() => setConfirmScan(false)}>
<DialogTitle>{LL.SCAN_DEVICES()}</DialogTitle>
<DialogContent dividers>{LL.EMS_SCAN()}</DialogContent>
<DialogActions>
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setConfirmScan(false)} color="secondary">
{LL.CANCEL()}
</Button>
<Button startIcon={<PermScanWifiIcon />} variant="outlined" onClick={scan} color="primary">
{LL.SCAN()}
</Button>
</DialogActions>
</Dialog>
);
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (
<>
<List>
<ListItem>
<ListItemAvatar>
<Avatar>
<BuildIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.EMS_ESP_VER()} secondary={data.emsesp_version} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#5f9a5f', color: 'white' }}>
<TimerIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.UPTIME()} secondary={formatDurationSec(data.uptime)} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: busStatusHighlight() }}>
<DirectionsBusIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.EMS_BUS_STATUS()} secondary={busStatus()} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: theme.palette.success.main }}>
<DeviceHubIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={LL.ACTIVE_DEVICES()}
secondary={
LL.NUM_DEVICES({ num: data.num_devices }) +
', ' +
LL.NUM_TEMP_SENSORS({ num: data.num_sensors }) +
', ' +
LL.NUM_ANALOG_SENSORS({ num: data.num_analogs })
}
/>
{me.admin && (
<Button
startIcon={<PermScanWifiIcon />}
variant="outlined"
color="primary"
onClick={() => setConfirmScan(true)}
>
{LL.SCAN_DEVICES()}
</Button>
)}
</ListItem>
<Divider variant="inset" component="li" />
</List>
{renderScanDialog()}
<Box mt={2} display="flex" flexWrap="wrap">
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
{LL.REFRESH()}
</Button>
</Box>
</>
);
};
return <SectionContent>{content()}</SectionContent>;
};
export default SystemStatus;

View File

@@ -32,12 +32,13 @@ const UploadDownload: FC = () => {
immediate: false
});
const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus, { force: true });
const { data: data, send: loadData, error } = useRequest(SystemApi.readESPSystemStatus, { force: true });
const { data: latestVersion } = useRequest(SystemApi.getStableVersion, {
immediate: true,
force: true
});
const { data: latestDevVersion } = useRequest(SystemApi.getDevVersion, {
immediate: true,
force: true