add module license

This commit is contained in:
proddy
2024-06-08 12:26:32 +02:00
parent 69d0cba893
commit 6a734b9c61
27 changed files with 1876 additions and 1221 deletions

View File

@@ -10,8 +10,10 @@ import MemoryIcon from '@mui/icons-material/Memory';
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import RefreshIcon from '@mui/icons-material/Refresh';
import RouterIcon from '@mui/icons-material/Router';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import TimerIcon from '@mui/icons-material/Timer';
import WifiIcon from '@mui/icons-material/Wifi';
import {
Avatar,
Box,
@@ -38,7 +40,7 @@ import ListMenuItem from 'components/layout/ListMenuItem';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import { busConnectionStatus } from 'project/types';
import { NTPSyncStatus } from 'types';
import { NTPSyncStatus, NetworkConnectionStatus } from 'types';
import RestartMonitor from './RestartMonitor';
@@ -151,6 +153,46 @@ const SystemStatus: FC = () => {
}
};
const networkStatusHighlight = () => {
switch (data.network_status) {
case NetworkConnectionStatus.WIFI_STATUS_IDLE:
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
return theme.palette.info.main;
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED:
return theme.palette.success.main;
case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED:
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
return theme.palette.error.main;
default:
return theme.palette.warning.main;
}
};
const networkStatus = () => {
switch (data.network_status) {
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
return LL.INACTIVE(1);
case NetworkConnectionStatus.WIFI_STATUS_IDLE:
return LL.IDLE();
case NetworkConnectionStatus.WIFI_STATUS_NO_SSID_AVAIL:
return 'No SSID Available';
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
return LL.CONNECTED(0) + ' (WiFi, ' + data.wifi_rssi + ' dBm)';
case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED:
return LL.CONNECTED(0) + ' (Ethernet)';
case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED:
return LL.CONNECTED(1) + ' ' + LL.FAILED(0);
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
return LL.CONNECTED(1) + ' ' + LL.LOST();
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
return LL.DISCONNECTED();
default:
return LL.UNKNOWN();
}
};
const activeHighlight = (value: boolean) =>
value ? theme.palette.success.main : theme.palette.info.main;
@@ -296,7 +338,7 @@ const SystemStatus: FC = () => {
<Button
startIcon={<PowerSettingsNewIcon />}
variant="outlined"
color="primary"
color="error"
onClick={() => setConfirmRestart(true)}
>
{LL.RESTART()}
@@ -355,6 +397,20 @@ const SystemStatus: FC = () => {
/>
<Divider variant="inset" component="li" />
<ListMenuItem
disabled={!me.admin}
icon={
data.network_status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED
? WifiIcon
: RouterIcon
}
bgcolor={networkStatusHighlight()}
label={LL.STATUS_OF(LL.NETWORK(1))}
text={networkStatus()}
to="/settings/network/status"
/>
<Divider variant="inset" component="li" />
<ListMenuItem
disabled={!me.admin}
icon={DeviceHubIcon}

View File

@@ -325,7 +325,10 @@ const de: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
MODULES: 'Module'
MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
};
export default de;

View File

@@ -325,7 +325,10 @@ const en: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings',
SECURITY_1: 'Add or remove users',
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware',
MODULES: 'Modules'
MODULES: 'Modules',
MODULES_UPDATED: 'Modules updated',
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules',
MODULES_NONE: 'No external modules detected'
};
export default en;

View File

@@ -325,7 +325,10 @@ const fr: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
MODULES: 'Modules' // TODO translate
MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
};
export default fr;

View File

@@ -325,7 +325,10 @@ const it: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
MODULES: 'Modules' // TODO translate
MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
};
export default it;

View File

@@ -325,7 +325,10 @@ const nl: Translation = {
APPLICATION_SETTINGS_1: 'Applicatie-instellingen wijzigen',
SECURITY_1: 'Gebruikers toevoegen of verwijderen',
UPLOAD_DOWNLOAD_1: 'Upload-/downloadinstellingen en firmware',
MODULES: 'Modules'
MODULES: 'Module',
MODULES_UPDATED: 'Modules geüpdatet',
MODULES_DESCRIPTION: 'Klik op de module om EMS-ESP library modules te activeren of te deactiveren',
MODULES_NONE: 'Geen externe modules gedetecteerd'
};
export default nl;

View File

@@ -325,7 +325,10 @@ const no: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
MODULES: 'Modules' // TODO translate
MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
};
export default no;

View File

@@ -325,7 +325,10 @@ const pl: BaseTranslation = {
APPLICATION_SETTINGS_1: 'Modyfikacja ustawień aplikacji EMS-ESP',
SECURITY_1: 'Dodawanie i usuwanie użytkowników',
UPLOAD_DOWNLOAD_1: 'Wysyłanie/pobieranie ustawień i firmware',
MODULES: 'Moduły'
MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
};
export default pl;

View File

@@ -325,7 +325,10 @@ const sk: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
MODULES: 'Modules' // TODO translate
MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
};
export default sk;

View File

@@ -325,7 +325,10 @@ const sv: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
MODULES: 'Modules' // TODO translate
MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
};
export default sv;

View File

@@ -325,7 +325,10 @@ const tr: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
MODULES: 'Modules' // TODO translate
MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
};
export default tr;

View File

@@ -1,13 +1,16 @@
import { useState } from 'react';
import { useCallback, useState } from 'react';
import type { FC } from 'react';
import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel';
import CircleIcon from '@mui/icons-material/Circle';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import WarningIcon from '@mui/icons-material/Warning';
import { Box, Button, Typography } from '@mui/material';
import * as SystemApi from 'api/system';
import {
Body,
Cell,
@@ -23,19 +26,28 @@ import {
BlockNavigation,
ButtonRow,
FormLoader,
MessageBox,
SectionContent,
useLayoutTitle
} from 'components';
import RestartMonitor from 'framework/system/RestartMonitor';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api';
import ModulesDialog from './ModulesDialog';
import type { ModuleItem, Modules } from './types';
const Modules: FC = () => {
const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0);
const [licenseChanges, setLicenseChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0);
const [selectedModuleItem, setSelectedModuleItem] = useState<ModuleItem>();
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
const [restarting, setRestarting] = useState<boolean>(false);
const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
const {
data: modules,
send: fetchModules,
@@ -44,6 +56,17 @@ const Modules: FC = () => {
initialData: []
});
const { send: restartCommand } = useRequest(SystemApi.restart(), {
immediate: false
});
const restart = async () => {
await restartCommand().catch((error: Error) => {
toast.error(error.message);
});
setRestarting(true);
};
const { send: writeModules } = useRequest(
(data: Modules) => EMSESP.writeModules(data),
{
@@ -53,7 +76,7 @@ const Modules: FC = () => {
const modules_theme = useTheme({
Table: `
--data-table-library_grid-template-columns: 48px 180px 120px 100px repeat(1, minmax(160px, 1fr));
--data-table-library_grid-template-columns: 48px 180px 120px 100px repeat(1, minmax(160px, 1fr)) 180px;
`,
BaseRow: `
font-size: 14px;
@@ -93,6 +116,20 @@ const Modules: FC = () => {
`
});
const onDialogClose = () => {
setDialogOpen(false);
};
const onDialogSave = (updatedItem: ModuleItem) => {
setDialogOpen(false);
updateModuleItem(updatedItem);
};
const editModuleItem = useCallback((mi: ModuleItem) => {
setSelectedModuleItem(mi);
setDialogOpen(true);
}, []);
const onCancel = async () => {
await fetchModules().then(() => {
setNumChanges(0);
@@ -100,16 +137,20 @@ const Modules: FC = () => {
};
function hasModulesChanged(mi: ModuleItem) {
return mi.enabled !== mi.o_enabled;
return mi.enabled !== mi.o_enabled || mi.license !== mi.o_license;
}
const selectModule = (updatedItem: ModuleItem) => {
updatedItem.enabled = !updatedItem.enabled;
function hasModuleLicenseChanged(mi: ModuleItem) {
return mi.license !== mi.o_license;
}
const updateModuleItem = (updatedItem: ModuleItem) => {
updateState('modules', (data: ModuleItem[]) => {
const new_data = data.map((mi) =>
mi.id === updatedItem.id ? { ...mi, ...updatedItem } : mi
);
setNumChanges(new_data.filter((mi) => hasModulesChanged(mi)).length);
setLicenseChanges(new_data.filter((mi) => hasModuleLicenseChanged(mi)).length);
return new_data;
});
};
@@ -117,12 +158,13 @@ const Modules: FC = () => {
const saveModules = async () => {
await writeModules({
modules: modules.map((condensed_mi) => ({
name: condensed_mi.name,
enabled: condensed_mi.enabled
key: condensed_mi.key,
enabled: condensed_mi.enabled,
license: condensed_mi.license
}))
})
.then(() => {
toast.success('Modules saved');
toast.success(LL.MODULES_UPDATED());
})
.catch((error: Error) => {
toast.error(error.message);
@@ -130,104 +172,134 @@ const Modules: FC = () => {
.finally(() => {
setNumChanges(0);
});
setRestartNeeded(licenseChanges > 0);
};
const renderModules = () => {
const renderContent = () => {
if (!modules) {
return <FormLoader onRetry={fetchModules} errorMessage={error?.message} />;
}
useLayoutTitle('Modules');
useLayoutTitle(LL.MODULES());
if (modules.length === 0) {
return (
<Typography variant="body2" color="error">
No modules detected
{LL.MODULES_NONE()}
</Typography>
);
}
const colorStatus = (status: number) => {
if (status === 1) {
return <div style={{ color: 'red' }}>Pending Activation</div>;
}
return <div style={{ color: '#00FF7F' }}>Activated</div>;
};
return (
<Table
data={{ nodes: modules }}
theme={modules_theme}
layout={{ custom: true }}
>
{(tableList: ModuleItem[]) => (
<>
<Header>
<HeaderRow>
<HeaderCell />
<HeaderCell stiff>{LL.NAME(0)}</HeaderCell>
<HeaderCell stiff>Author</HeaderCell>
<HeaderCell stiff>{LL.VERSION()}</HeaderCell>
<HeaderCell stiff>{LL.STATUS_OF('')}</HeaderCell>
</HeaderRow>
</Header>
<Body>
{tableList.map((mi: ModuleItem) => (
<Row key={mi.id} item={mi} onClick={() => selectModule(mi)}>
<Cell stiff>
{mi.enabled ? (
<CircleIcon
color="success"
sx={{ fontSize: 16, verticalAlign: 'middle' }}
/>
) : (
<CircleIcon
color="error"
sx={{ fontSize: 16, verticalAlign: 'middle' }}
/>
)}
</Cell>
<Cell>{mi.name}</Cell>
<Cell>{mi.author}</Cell>
<Cell>{mi.version}</Cell>
<Cell>{mi.status}</Cell>
</Row>
))}
</Body>
</>
<>
<Box mb={2} color="warning.main">
<Typography variant="body2">{LL.MODULES_DESCRIPTION()}</Typography>
</Box>
<Table
data={{ nodes: modules }}
theme={modules_theme}
layout={{ custom: true }}
>
{(tableList: ModuleItem[]) => (
<>
<Header>
<HeaderRow>
<HeaderCell />
<HeaderCell>{LL.NAME(0)}</HeaderCell>
<HeaderCell>Author</HeaderCell>
<HeaderCell>{LL.VERSION()}</HeaderCell>
<HeaderCell>Message</HeaderCell>
<HeaderCell>{LL.STATUS_OF('')}</HeaderCell>
</HeaderRow>
</Header>
<Body>
{tableList.map((mi: ModuleItem) => (
<Row key={mi.id} item={mi} onClick={() => editModuleItem(mi)}>
<Cell stiff>
{mi.enabled ? (
<CircleIcon
color="success"
sx={{ fontSize: 16, verticalAlign: 'middle' }}
/>
) : (
<CircleIcon
color="error"
sx={{ fontSize: 16, verticalAlign: 'middle' }}
/>
)}
</Cell>
<Cell>{mi.name}</Cell>
<Cell>{mi.author}</Cell>
<Cell>{mi.version}</Cell>
<Cell>{mi.message}</Cell>
<Cell>{colorStatus(mi.status)}</Cell>
</Row>
))}
</Body>
</>
)}
</Table>
{restartNeeded ? (
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
<Button
startIcon={<PowerSettingsNewIcon />}
variant="contained"
color="error"
onClick={restart}
>
{LL.RESTART()}
</Button>
</MessageBox>
) : (
<Box mt={1} display="flex" flexWrap="wrap">
<Box flexGrow={1}>
{numChanges !== 0 && (
<ButtonRow>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={onCancel}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<WarningIcon color="warning" />}
variant="contained"
color="info"
onClick={saveModules}
>
{LL.APPLY_CHANGES(numChanges)}
</Button>
</ButtonRow>
)}
</Box>
</Box>
)}
</Table>
</>
);
};
return (
<SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
<Box mb={2} color="warning.main">
<Typography variant="body2">
Activate or de-activate EMS-ESP library modules by selecting (**
experimental **)
</Typography>
</Box>
{renderModules()}
<Box mt={1} display="flex" flexWrap="wrap">
<Box flexGrow={1}>
{numChanges !== 0 && (
<ButtonRow>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={onCancel}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<WarningIcon color="warning" />}
variant="contained"
color="info"
onClick={saveModules}
>
{LL.APPLY_CHANGES(numChanges)}
</Button>
</ButtonRow>
)}
</Box>
</Box>
{restarting ? <RestartMonitor /> : renderContent()}
{selectedModuleItem && (
<ModulesDialog
open={dialogOpen}
onClose={onDialogClose}
onSave={onDialogSave}
selectedItem={selectedModuleItem}
/>
)}
</SectionContent>
);
};

View File

@@ -0,0 +1,108 @@
import { useEffect, useState } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import DoneIcon from '@mui/icons-material/Done';
import {
Box,
Button,
Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Grid,
TextField
} from '@mui/material';
import { dialogStyle } from 'CustomTheme';
import { BlockFormControlLabel } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { updateValue } from 'utils';
import type { ModuleItem } from './types';
interface ModulesDialogProps {
open: boolean;
onClose: () => void;
onSave: (mi: ModuleItem) => void;
selectedItem: ModuleItem;
}
const ModulesDialog = ({
open,
onClose,
onSave,
selectedItem
}: ModulesDialogProps) => {
const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<ModuleItem>(selectedItem);
const updateFormValue = updateValue(setEditItem);
useEffect(() => {
if (open) {
setEditItem(selectedItem);
}
}, [open, selectedItem]);
const close = () => {
onClose();
};
const save = () => {
onSave(editItem);
};
return (
<Dialog sx={dialogStyle} open={open} onClose={onClose}>
<DialogTitle>
{LL.EDIT() + ' ' + LL.MODULES() + ' : ' + editItem.key}
</DialogTitle>
<DialogContent dividers>
<Grid container>
<BlockFormControlLabel
control={
<Checkbox
checked={editItem.enabled}
onChange={updateFormValue}
name="enabled"
/>
}
label={LL.ACTIVE()}
/>
</Grid>
<Box mt={1} mb={1}>
<TextField
name="license"
label="License Key"
multiline
rows={6}
fullWidth
value={editItem.license}
onChange={updateFormValue}
/>
</Box>
</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={close}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<DoneIcon />}
variant="outlined"
onClick={save}
color="primary"
>
{LL.UPDATE()}
</Button>
</DialogActions>
</Dialog>
);
};
export default ModulesDialog;

View File

@@ -113,7 +113,8 @@ export const readModules = () =>
transformData(data) {
return (data as Modules).modules.map((mi: ModuleItem) => ({
...mi,
o_enabled: mi.enabled
o_enabled: mi.enabled,
o_license: mi.license
}));
}
});

View File

@@ -310,12 +310,16 @@ export interface Schedule {
export interface ModuleItem {
id: number; // unique index
key: string;
name: string;
author: string;
version: string;
status: string;
status: number;
message: string;
enabled: boolean;
license: string;
o_enabled?: boolean;
o_license?: string;
}
export interface Modules {

View File

@@ -1,5 +1,7 @@
import type { busConnectionStatus } from 'project/types';
import type { NetworkConnectionStatus } from './network';
export interface ESPSystemStatus {
emsesp_version: string;
esp_platform: string;
@@ -37,6 +39,8 @@ export interface SystemStatus {
ntp_status: number;
mqtt_status: boolean;
ap_status: boolean;
network_status: NetworkConnectionStatus;
wifi_rssi: number;
}
export enum LogLevel {