translate system menu

This commit is contained in:
proddy
2022-08-28 10:44:22 +02:00
parent 7122e878a5
commit d8e324a005
16 changed files with 366 additions and 83 deletions

View File

@@ -31,6 +31,8 @@ import { ButtonRow, FormLoader, SectionContent } from '../../components';
import { extractErrorMessage, formatDateTime, formatLocalDateTime, useRest } from '../../utils';
import { AuthenticatedContext } from '../../contexts/authentication';
import { useI18nContext } from '../../i18n/i18n-react';
export const isNtpActive = ({ status }: NTPStatus) => status === NTPSyncStatus.NTP_ACTIVE;
export const isNtpEnabled = ({ status }: NTPStatus) => status !== NTPSyncStatus.NTP_DISABLED;
@@ -68,6 +70,8 @@ const NTPStatusForm: FC = () => {
const { enqueueSnackbar } = useSnackbar();
const { me } = useContext(AuthenticatedContext);
const { LL } = useI18nContext();
const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) => setLocalTime(event.target.value);
const openSetTime = () => {
@@ -83,11 +87,11 @@ const NTPStatusForm: FC = () => {
await NTPApi.updateTime({
local_time: formatLocalDateTime(new Date(localTime))
});
enqueueSnackbar('Time set', { variant: 'success' });
enqueueSnackbar(LL.TIME_SET(), { variant: 'success' });
setSettingTime(false);
loadData();
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem updating time'), { variant: 'error' });
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally {
setProcessing(false);
}

View File

@@ -14,6 +14,8 @@ import { extractErrorMessage } from '../../utils';
import * as EMSESP from '../../project/api';
import { useI18nContext } from '../../i18n/i18n-react';
interface UploadFileProps {
uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
}
@@ -23,6 +25,8 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
const { enqueueSnackbar } = useSnackbar();
const { LL } = useI18nContext();
const saveFile = (json: any, endpoint: string) => {
const a = document.createElement('a');
const filename = 'emsesp_' + endpoint + '.json';
@@ -35,19 +39,19 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
enqueueSnackbar('File downloaded', { variant: 'info' });
enqueueSnackbar(LL.DOWNLOAD_SUCCESSFUL(), { variant: 'info' });
};
const downloadSettings = async () => {
try {
const response = await EMSESP.getSettings();
if (response.status !== 200) {
enqueueSnackbar('Unable to get settings', { variant: 'error' });
enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
} else {
saveFile(response.data, 'settings');
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
}
};
@@ -55,47 +59,43 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
try {
const response = await EMSESP.getCustomizations();
if (response.status !== 200) {
enqueueSnackbar('Unable to get customizations', { variant: 'error' });
enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
} else {
saveFile(response.data, 'customizations');
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
}
};
return (
<>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
Upload
{LL.UPLOAD()}
</Typography>
{!uploading && (
<Box mb={2} color="warning.main">
<Typography variant="body2">
Upload a new firmware (.bin) file, settings or customizations (.json) file below
</Typography>
<Typography variant="body2">{LL.UPLOAD_TEXT()} </Typography>
</Box>
)}
<SingleUpload onDrop={uploadFile} onCancel={cancelUpload} uploading={uploading} progress={uploadProgress} />
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
Download
{LL.DOWNLOAD()}
</Typography>
{!uploading && (
<>
<Box color="warning.main">
<Typography mb={1} variant="body2">
Download the application settings. Be careful when sharing your settings as this file contains passwords
and other sensitive system information
{LL.DOWNLOAD_SETTINGS_TEXT()}
</Typography>
</Box>
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => downloadSettings()}>
settings
{LL.SETTINGS()}
</Button>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
Download the entity customizations
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '}
</Typography>
</Box>
<Button
@@ -104,7 +104,7 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
color="primary"
onClick={() => downloadCustomizations()}
>
customizations
{LL.CUSTOMIZATION()}
</Button>
</>
)}

View File

@@ -12,6 +12,7 @@ import {
ValidatedPasswordField,
ValidatedTextField
} from '../../components';
import { OTASettings } from '../../types';
import { numberValue, updateValue, useRest } from '../../utils';
@@ -19,12 +20,16 @@ import { ValidateFieldsError } from 'async-validator';
import { validate } from '../../validators';
import { OTA_SETTINGS_VALIDATOR } from '../../validators/system';
import { useI18nContext } from '../../i18n/i18n-react';
const OTASettingsForm: FC = () => {
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<OTASettings>({
read: SystemApi.readOTASettings,
update: SystemApi.updateOTASettings
});
const { LL } = useI18nContext();
const updateFormValue = updateValue(setData);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -48,7 +53,7 @@ const OTASettingsForm: FC = () => {
<>
<BlockFormControlLabel
control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />}
label="Enable OTA Updates"
label={LL.ENABLE_OTA()}
/>
<ValidatedTextField
fieldErrors={fieldErrors}
@@ -64,7 +69,7 @@ const OTASettingsForm: FC = () => {
<ValidatedPasswordField
fieldErrors={fieldErrors}
name="password"
label="Password"
label={LL.PASSWORD()}
fullWidth
variant="outlined"
value={data.password}
@@ -80,7 +85,7 @@ const OTASettingsForm: FC = () => {
type="submit"
onClick={validateAndSubmit}
>
Save
{LL.SAVE()}
</Button>
</ButtonRow>
</>
@@ -88,7 +93,7 @@ const OTASettingsForm: FC = () => {
};
return (
<SectionContent title="OTA Settings" titleGutter>
<SectionContent title={'OTA ' + LL.SETTINGS()} titleGutter>
{content()}
</SectionContent>
);

View File

@@ -4,6 +4,8 @@ import { FC, useRef, useState } from 'react';
import * as SystemApi from '../../api/system';
import { FormLoader } from '../../components';
import { useI18nContext } from '../../i18n/i18n-react';
const RESTART_TIMEOUT = 2 * 60 * 1000;
const POLL_TIMEOUT = 2000;
const POLL_INTERVAL = 5000;
@@ -12,6 +14,8 @@ const RestartMonitor: FC = () => {
const [failed, setFailed] = useState<boolean>(false);
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
const { LL } = useI18nContext();
const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT);
const poll = useRef(async () => {
try {
@@ -32,12 +36,7 @@ const RestartMonitor: FC = () => {
useEffect(() => () => timeoutId && clearTimeout(timeoutId), [timeoutId]);
return (
<FormLoader
message="EMS-ESP is restarting, please wait&hellip;"
errorMessage={failed ? 'Timed out waiting for device to restart.' : undefined}
/>
);
return <FormLoader message={LL.APPLICATION_RESTARTING() + '...'} errorMessage={failed ? 'Timed out' : undefined} />;
};
export default RestartMonitor;

View File

@@ -12,8 +12,13 @@ import OTASettingsForm from './OTASettingsForm';
import SystemLog from './SystemLog';
import { useI18nContext } from '../../i18n/i18n-react';
const System: FC = () => {
useLayoutTitle('System');
const { LL } = useI18nContext();
useLayoutTitle(LL.SYSTEM());
const { me } = useContext(AuthenticatedContext);
const { features } = useContext(FeaturesContext);
@@ -22,11 +27,11 @@ const System: FC = () => {
return (
<>
<RouterTabs value={routerTab}>
<Tab value="status" label="System Status" />
<Tab value="log" label="System Log" />
<Tab value="status" label={LL.SYSTEM() + ' ' + LL.STATUS()} />
<Tab value="log" label={LL.SYSTEM() + ' ' + LL.LOG()} />
{features.ota && <Tab value="ota" label="OTA Settings" disabled={!me.admin} />}
{features.upload_firmware && <Tab value="upload" label="Upload/Download" disabled={!me.admin} />}
{features.ota && <Tab value="ota" label={"OTA " + LL.SETTINGS()} disabled={!me.admin} />}
{features.upload_firmware && <Tab value="upload" label={LL.UPLOAD_DOWNLOAD()} disabled={!me.admin} />}
</RouterTabs>
<Routes>
<Route path="status" element={<SystemStatusForm />} />

View File

@@ -15,6 +15,9 @@ import DownloadIcon from '@mui/icons-material/GetApp';
import { useSnackbar } from 'notistack';
import { EVENT_SOURCE_ROOT } from '../../api/endpoints';
import { useI18nContext } from '../../i18n/i18n-react';
export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log';
const useWindowSize = () => {
@@ -63,6 +66,8 @@ const levelLabel = (level: LogLevel) => {
const SystemLog: FC = () => {
useWindowSize();
const { LL } = useI18nContext();
const { loadData, data, setData } = useRest<LogSettings>({
read: SystemApi.readLogSettings
});
@@ -104,10 +109,10 @@ const SystemLog: FC = () => {
compact: data.compact
});
if (response.status !== 200) {
enqueueSnackbar('Problem applying log settings', { variant: 'error' });
enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' });
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem applying log settings'), { variant: 'error' });
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
}
}
};
@@ -159,9 +164,9 @@ const SystemLog: FC = () => {
try {
setLogEntries((await SystemApi.readLogEntries()).data);
} catch (error: unknown) {
setErrorMessage(extractErrorMessage(error, 'Failed to fetch log'));
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
}, []);
}, [LL]);
useEffect(() => {
fetchLog();
@@ -214,7 +219,7 @@ const SystemLog: FC = () => {
</ValidatedTextField>
</Grid>
<Grid item xs={3}>
<FormLabel>Buffer size</FormLabel>
<FormLabel>{LL.BUFFER_SIZE()}</FormLabel>
<Slider
value={data.max_messages}
valueLabelDisplay="auto"
@@ -235,12 +240,12 @@ const SystemLog: FC = () => {
<Grid item>
<BlockFormControlLabel
control={<Checkbox checked={data.compact} onChange={updateFormValue} name="compact" />}
label="Compact"
label={LL.COMPACT()}
/>
</Grid>
<Grid item>
<Button startIcon={<DownloadIcon />} variant="outlined" color="secondary" onClick={onDownload}>
Export
{LL.EXPORT()}
</Button>
</Grid>
</Grid>
@@ -273,7 +278,7 @@ const SystemLog: FC = () => {
};
return (
<SectionContent title="System Log" titleGutter id="log-window">
<SectionContent title={LL.SYSTEM() + ' ' + LL.LOG()} titleGutter id="log-window">
{content()}
</SectionContent>
);

View File

@@ -39,6 +39,8 @@ import { AuthenticatedContext } from '../../contexts/authentication';
import axios from 'axios';
import { useI18nContext } from '../../i18n/i18n-react';
export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest';
export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest';
export const uploadURL = window.location.origin + '/system/upload';
@@ -48,6 +50,8 @@ function formatNumber(num: number) {
}
const SystemStatusForm: FC = () => {
const { LL } = useI18nContext();
const { loadData, data, errorMessage } = useRest<SystemStatus>({ read: SystemApi.readSystemStatus });
const { me } = useContext(AuthenticatedContext);
@@ -80,9 +84,9 @@ const SystemStatusForm: FC = () => {
setProcessing(true);
try {
await SystemApi.restart();
enqueueSnackbar('EMS-ESP is restarting...', { variant: 'info' });
enqueueSnackbar(LL.APPLICATION_RESTARTING(), { variant: 'info' });
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem restarting device'), { variant: 'error' });
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} finally {
setConfirmRestart(false);
setProcessing(false);
@@ -92,7 +96,7 @@ const SystemStatusForm: FC = () => {
const renderRestartDialog = () => (
<Dialog open={confirmRestart} onClose={() => setConfirmRestart(false)}>
<DialogTitle>Restart</DialogTitle>
<DialogContent dividers>Are you sure you want to restart EMS-ESP?</DialogContent>
<DialogContent dividers>{LL.RESTART_TEXT()}</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
@@ -119,16 +123,12 @@ const SystemStatusForm: FC = () => {
const renderVersionDialog = () => {
return (
<Dialog open={showingVersion} onClose={() => setShowingVersion(false)}>
<DialogTitle>Version Check</DialogTitle>
<DialogTitle>{LL.VERSION_CHECK()}</DialogTitle>
<DialogContent dividers>
<MessageBox
my={0}
level="info"
message={'You are currently running EMS-ESP version ' + data?.emsesp_version}
/>
<MessageBox my={0} level="info" message={LL.SYSTEM_VERSION_RUNNING() + ' ' + data?.emsesp_version} />
{latestVersion && (
<Box mt={2} mb={2}>
The latest <u>official</u> version is <b>{latestVersion.version}</b>&nbsp;(
{LL.THE_LATEST()}&nbsp;<u>official</u> version is <b>{latestVersion.version}</b>&nbsp;(
<Link target="_blank" href={latestVersion.changelog} color="primary">
{'release notes'}
</Link>
@@ -142,7 +142,7 @@ const SystemStatusForm: FC = () => {
{latestDevVersion && (
<Box mt={2} mb={2}>
The latest <u>development</u> version is&nbsp;<b>{latestDevVersion.version}</b>
{LL.THE_LATEST()}&nbsp;<u>development</u> version is&nbsp;<b>{latestDevVersion.version}</b>
&nbsp;(
<Link target="_blank" href={latestDevVersion.changelog} color="primary">
{'release notes'}
@@ -157,17 +157,17 @@ const SystemStatusForm: FC = () => {
<Box color="warning.main" p={0} pl={0} pr={0} mt={4} mb={0}>
<Typography variant="body2">
Use&nbsp;
{LL.USE()}&nbsp;
<Link target="_blank" href={uploadURL} color="primary">
{'UPLOAD'}
</Link>
&nbsp;to apply the new firmware
&nbsp;{LL.SYSTEM_APPLY_FIRMWARE()}
</Typography>
</Box>
</DialogContent>
<DialogActions>
<Button variant="outlined" onClick={() => setShowingVersion(false)} color="secondary">
Close
{LL.CLOSE()}
</Button>
</DialogActions>
</Dialog>
@@ -178,9 +178,9 @@ const SystemStatusForm: FC = () => {
setProcessing(true);
try {
await SystemApi.factoryReset();
enqueueSnackbar('Device has been factory reset and will now restart', { variant: 'info' });
enqueueSnackbar(LL.SYSTEM_FACTORY_TEXT(), { variant: 'info' });
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem factory resetting the device'), { variant: 'error' });
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally {
setConfirmFactoryReset(false);
setProcessing(false);
@@ -189,8 +189,8 @@ const SystemStatusForm: FC = () => {
const renderFactoryResetDialog = () => (
<Dialog open={confirmFactoryReset} onClose={() => setConfirmFactoryReset(false)}>
<DialogTitle>Factory Reset</DialogTitle>
<DialogContent dividers>Are you sure you want to reset the device to its factory defaults?</DialogContent>
<DialogTitle>{LL.FACTORY_RESET()}</DialogTitle>
<DialogContent dividers>{LL.SYSTEM_FACTORY_TEXT_DIALOG()}</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
@@ -198,7 +198,7 @@ const SystemStatusForm: FC = () => {
onClick={() => setConfirmFactoryReset(false)}
color="secondary"
>
Cancel
{LL.CANCEL()}
</Button>
<Button
startIcon={<SettingsBackupRestoreIcon />}
@@ -208,7 +208,7 @@ const SystemStatusForm: FC = () => {
autoFocus
color="error"
>
Factory Reset
{LL.FACTORY_RESET()}
</Button>
</DialogActions>
</Dialog>
@@ -231,7 +231,7 @@ const SystemStatusForm: FC = () => {
<ListItemText primary="EMS-ESP Version" secondary={'v' + data.emsesp_version} />
{latestVersion && (
<Button color="primary" onClick={() => setShowingVersion(true)}>
Version Check
{LL.VERSION_CHECK()}
</Button>
)}
</ListItem>
@@ -335,7 +335,7 @@ const SystemStatusForm: FC = () => {
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
<ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
Refresh
{LL.REFRESH()}
</Button>
</ButtonRow>
</Box>
@@ -348,7 +348,7 @@ const SystemStatusForm: FC = () => {
color="primary"
onClick={() => setConfirmRestart(true)}
>
Restart
{LL.RESTART()}
</Button>
<Button
startIcon={<SettingsBackupRestoreIcon />}
@@ -356,7 +356,7 @@ const SystemStatusForm: FC = () => {
onClick={() => setConfirmFactoryReset(true)}
color="error"
>
Factory reset
{LL.FACTORY_RESET()}
</Button>
</ButtonRow>
</Box>
@@ -370,7 +370,7 @@ const SystemStatusForm: FC = () => {
};
return (
<SectionContent title="System Status" titleGutter>
<SectionContent title={LL.SYSTEM() + ' ' + LL.STATUS()} titleGutter>
{content()}
</SectionContent>
);

View File

@@ -7,9 +7,13 @@ import { FileUploadConfig } from '../../api/endpoints';
import GeneralFileUpload from './GeneralFileUpload';
import RestartMonitor from './RestartMonitor';
import { useI18nContext } from '../../i18n/i18n-react';
const UploadFileForm: FC = () => {
const [restarting, setRestarting] = useState<boolean>();
const { LL } = useI18nContext();
const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => {
const response = await SystemApi.uploadFile(file, config);
setRestarting(true);
@@ -17,7 +21,7 @@ const UploadFileForm: FC = () => {
});
return (
<SectionContent title="Upload/Download" titleGutter>
<SectionContent title={LL.UPLOAD_DOWNLOAD()} titleGutter>
{restarting ? <RestartMonitor /> : <GeneralFileUpload uploadGeneralFile={uploadFile.current} />}
</SectionContent>
);