translate NTP, AP, MQTT and User

This commit is contained in:
Proddy
2022-08-28 15:06:20 +02:00
parent d8e324a005
commit 3ea71e1dfb
18 changed files with 689 additions and 171 deletions

View File

@@ -32,8 +32,8 @@ const LayoutMenu: FC = () => {
)} )}
<List disablePadding component="nav"> <List disablePadding component="nav">
<LayoutMenuItem icon={SettingsEthernetIcon} label={LL.NETWORK_CONNECTION()} to="/network" /> <LayoutMenuItem icon={SettingsEthernetIcon} label={LL.NETWORK_CONNECTION()} to="/network" />
<LayoutMenuItem icon={SettingsInputAntennaIcon} label="Access Point" to="/ap" /> <LayoutMenuItem icon={SettingsInputAntennaIcon} label={LL.ACCESS_POINT()} to="/ap" />
{features.ntp && <LayoutMenuItem icon={AccessTimeIcon} label={LL.NETWORK_TIME()} to="/ntp" />} {features.ntp && <LayoutMenuItem icon={AccessTimeIcon} label="NTP" to="/ntp" />}
{features.mqtt && <LayoutMenuItem icon={DeviceHubIcon} label="MQTT" to="/mqtt" />} {features.mqtt && <LayoutMenuItem icon={DeviceHubIcon} label="MQTT" to="/mqtt" />}
<LayoutMenuItem <LayoutMenuItem
icon={LockIcon} icon={LockIcon}

View File

@@ -19,6 +19,8 @@ import { APProvisionMode, APSettings } from '../../types';
import { numberValue, updateValue, useRest } from '../../utils'; import { numberValue, updateValue, useRest } from '../../utils';
import * as APApi from '../../api/ap'; import * as APApi from '../../api/ap';
import { useI18nContext } from '../../i18n/i18n-react';
export const isAPEnabled = ({ provision_mode }: APSettings) => { export const isAPEnabled = ({ provision_mode }: APSettings) => {
return provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED; return provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
}; };
@@ -29,6 +31,8 @@ const APSettingsForm: FC = () => {
update: APApi.updateAPSettings update: APApi.updateAPSettings
}); });
const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const updateFormValue = updateValue(setData); const updateFormValue = updateValue(setData);
@@ -53,7 +57,7 @@ const APSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="provision_mode" name="provision_mode"
label="Provide Access Point&hellip;" label={LL.AP_PROVIDE() + '...'}
value={data.provision_mode} value={data.provision_mode}
fullWidth fullWidth
select select
@@ -61,16 +65,16 @@ const APSettingsForm: FC = () => {
onChange={updateFormValue} onChange={updateFormValue}
margin="normal" margin="normal"
> >
<MenuItem value={APProvisionMode.AP_MODE_ALWAYS}>Always</MenuItem> <MenuItem value={APProvisionMode.AP_MODE_ALWAYS}>{LL.AP_PROVIDE_TEXT_1()}</MenuItem>
<MenuItem value={APProvisionMode.AP_MODE_DISCONNECTED}>When WiFi Disconnected</MenuItem> <MenuItem value={APProvisionMode.AP_MODE_DISCONNECTED}>{LL.AP_PROVIDE_TEXT_2()}</MenuItem>
<MenuItem value={APProvisionMode.AP_NEVER}>Never</MenuItem> <MenuItem value={APProvisionMode.AP_NEVER}>{LL.AP_PROVIDE_TEXT_3()}</MenuItem>
</ValidatedTextField> </ValidatedTextField>
{isAPEnabled(data) && ( {isAPEnabled(data) && (
<> <>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="ssid" name="ssid"
label="Access Point SSID" label={LL.ACCESS_POINT() + ' SSID'}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.ssid} value={data.ssid}
@@ -80,7 +84,7 @@ const APSettingsForm: FC = () => {
<ValidatedPasswordField <ValidatedPasswordField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="password" name="password"
label="Access Point Password" label={LL.ACCESS_POINT() + ' ' + LL.PASSWORD()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.password} value={data.password}
@@ -90,7 +94,7 @@ const APSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="channel" name="channel"
label="Preferred Channel" label={LL.AP_PREFERRED_CHANNEL()}
value={numberValue(data.channel)} value={numberValue(data.channel)}
fullWidth fullWidth
select select
@@ -107,7 +111,7 @@ const APSettingsForm: FC = () => {
</ValidatedTextField> </ValidatedTextField>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="ssid_hidden" checked={data.ssid_hidden} onChange={updateFormValue} />} control={<Checkbox name="ssid_hidden" checked={data.ssid_hidden} onChange={updateFormValue} />}
label="Hide SSID" label={LL.AP_HIDE_SSID()}
/> />
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
@@ -168,7 +172,7 @@ const APSettingsForm: FC = () => {
type="submit" type="submit"
onClick={validateAndSubmit} onClick={validateAndSubmit}
> >
Save {LL.SAVE()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</> </>
@@ -176,7 +180,7 @@ const APSettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title="Access Point Settings" titleGutter> <SectionContent title={LL.ACCESS_POINT() + ' ' + LL.SETTINGS()} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -11,6 +11,8 @@ import { APNetworkStatus, APStatus } from '../../types';
import { ButtonRow, FormLoader, SectionContent } from '../../components'; import { ButtonRow, FormLoader, SectionContent } from '../../components';
import { useRest } from '../../utils'; import { useRest } from '../../utils';
import { useI18nContext } from '../../i18n/i18n-react';
export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => { export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
switch (status) { switch (status) {
case APNetworkStatus.ACTIVE: case APNetworkStatus.ACTIVE:
@@ -24,24 +26,26 @@ export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
} }
}; };
export const apStatus = ({ status }: APStatus) => {
switch (status) {
case APNetworkStatus.ACTIVE:
return 'Active';
case APNetworkStatus.INACTIVE:
return 'Inactive';
case APNetworkStatus.LINGERING:
return 'Lingering until idle';
default:
return 'Unknown';
}
};
const APStatusForm: FC = () => { const APStatusForm: FC = () => {
const { loadData, data, errorMessage } = useRest<APStatus>({ read: APApi.readAPStatus }); const { loadData, data, errorMessage } = useRest<APStatus>({ read: APApi.readAPStatus });
const { LL } = useI18nContext();
const theme = useTheme(); const theme = useTheme();
const apStatus = ({ status }: APStatus) => {
switch (status) {
case APNetworkStatus.ACTIVE:
return LL.ACTIVE();
case APNetworkStatus.INACTIVE:
return LL.INACTIVE();
case APNetworkStatus.LINGERING:
return 'Lingering until idle';
default:
return LL.UNKNOWN();
}
};
const content = () => { const content = () => {
if (!data) { if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />; return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
@@ -56,7 +60,7 @@ const APStatusForm: FC = () => {
<SettingsInputAntennaIcon /> <SettingsInputAntennaIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Status" secondary={apStatus(data)} /> <ListItemText primary={LL.STATUS()} secondary={apStatus(data)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -87,7 +91,7 @@ const APStatusForm: FC = () => {
</List> </List>
<ButtonRow> <ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}> <Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
Refresh {LL.REFRESH()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</> </>
@@ -95,7 +99,7 @@ const APStatusForm: FC = () => {
}; };
return ( return (
<SectionContent title="Access Point Status" titleGutter> <SectionContent title={LL.ACCESS_POINT() + ' ' + LL.STATUS()} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -8,8 +8,12 @@ import APStatusForm from './APStatusForm';
import APSettingsForm from './APSettingsForm'; import APSettingsForm from './APSettingsForm';
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from '../../components'; import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from '../../components';
import { useI18nContext } from '../../i18n/i18n-react';
const AccessPoint: FC = () => { const AccessPoint: FC = () => {
useLayoutTitle('Access Point'); const { LL } = useI18nContext();
useLayoutTitle(LL.ACCESS_POINT());
const authenticatedContext = useContext(AuthenticatedContext); const authenticatedContext = useContext(AuthenticatedContext);
@@ -18,8 +22,12 @@ const AccessPoint: FC = () => {
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="status" label="Access Point Status" /> <Tab value="status" label={LL.ACCESS_POINT() + ' ' + LL.STATUS()} />
<Tab value="settings" label="Access Point Settings" disabled={!authenticatedContext.me.admin} /> <Tab
value="settings"
label={LL.ACCESS_POINT() + ' ' + LL.SETTINGS()}
disabled={!authenticatedContext.me.admin}
/>
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<APStatusForm />} /> <Route path="status" element={<APStatusForm />} />

View File

@@ -9,7 +9,11 @@ import { AuthenticatedContext } from '../../contexts/authentication';
import MqttStatusForm from './MqttStatusForm'; import MqttStatusForm from './MqttStatusForm';
import MqttSettingsForm from './MqttSettingsForm'; import MqttSettingsForm from './MqttSettingsForm';
import { useI18nContext } from '../../i18n/i18n-react';
const Mqtt: FC = () => { const Mqtt: FC = () => {
const { LL } = useI18nContext();
useLayoutTitle('MQTT'); useLayoutTitle('MQTT');
const authenticatedContext = useContext(AuthenticatedContext); const authenticatedContext = useContext(AuthenticatedContext);
@@ -18,8 +22,8 @@ const Mqtt: FC = () => {
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="status" label="MQTT Status" /> <Tab value="status" label={'MQTT ' + LL.STATUS()} />
<Tab value="settings" label="MQTT Settings" disabled={!authenticatedContext.me.admin} /> <Tab value="settings" label={'MQTT ' + LL.SETTINGS()} disabled={!authenticatedContext.me.admin} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<MqttStatusForm />} /> <Route path="status" element={<MqttStatusForm />} />

View File

@@ -17,12 +17,16 @@ import { MqttSettings } from '../../types';
import { numberValue, updateValue, useRest } from '../../utils'; import { numberValue, updateValue, useRest } from '../../utils';
import * as MqttApi from '../../api/mqtt'; import * as MqttApi from '../../api/mqtt';
import { useI18nContext } from '../../i18n/i18n-react';
const MqttSettingsForm: FC = () => { const MqttSettingsForm: FC = () => {
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<MqttSettings>({ const { loadData, saving, data, setData, saveData, errorMessage } = useRest<MqttSettings>({
read: MqttApi.readMqttSettings, read: MqttApi.readMqttSettings,
update: MqttApi.updateMqttSettings update: MqttApi.updateMqttSettings
}); });
const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const updateFormValue = updateValue(setData); const updateFormValue = updateValue(setData);
@@ -46,7 +50,7 @@ const MqttSettingsForm: FC = () => {
<> <>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />} control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />}
label="Enable MQTT" label={LL.ENABLE_MQTT()}
/> />
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start"> <Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={6}> <Grid item xs={6}>
@@ -91,7 +95,7 @@ const MqttSettingsForm: FC = () => {
<Grid item xs={6}> <Grid item xs={6}>
<ValidatedTextField <ValidatedTextField
name="client_id" name="client_id"
label="Client ID (optional)" label={'Client ID (' + LL.OPTIONAL() + ')'}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.client_id} value={data.client_id}
@@ -104,7 +108,7 @@ const MqttSettingsForm: FC = () => {
<Grid item xs={6}> <Grid item xs={6}>
<ValidatedTextField <ValidatedTextField
name="username" name="username"
label="Username" label={LL.USERNAME()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.username} value={data.username}
@@ -115,7 +119,7 @@ const MqttSettingsForm: FC = () => {
<Grid item xs={6}> <Grid item xs={6}>
<ValidatedPasswordField <ValidatedPasswordField
name="password" name="password"
label="Password" label={LL.PASSWORD()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.password} value={data.password}
@@ -160,18 +164,18 @@ const MqttSettingsForm: FC = () => {
</Grid> </Grid>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="clean_session" checked={data.clean_session} onChange={updateFormValue} />} control={<Checkbox name="clean_session" checked={data.clean_session} onChange={updateFormValue} />}
label="Set Clean Session" label={LL.MQTT_CLEAN_SESSION()}
/> />
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="mqtt_retain" checked={data.mqtt_retain} onChange={updateFormValue} />} control={<Checkbox name="mqtt_retain" checked={data.mqtt_retain} onChange={updateFormValue} />}
label="Always use Retain Flag" label={LL.MQTT_RETAIN_FLAG()}
/> />
<Typography sx={{ pt: 2 }} variant="h6" color="primary"> <Typography sx={{ pt: 2 }} variant="h6" color="primary">
Formatting {LL.FORMATTING()}
</Typography> </Typography>
<ValidatedTextField <ValidatedTextField
name="nested_format" name="nested_format"
label="Topic/Payload Format" label={'Topic/Payload ' + LL.FORMAT()}
value={data.nested_format} value={data.nested_format}
fullWidth fullWidth
variant="outlined" variant="outlined"
@@ -179,19 +183,19 @@ const MqttSettingsForm: FC = () => {
margin="normal" margin="normal"
select select
> >
<MenuItem value={1}>Nested in a single topic</MenuItem> <MenuItem value={1}>{LL.MQTT_NEST_1()}</MenuItem>
<MenuItem value={2}>As individual topics</MenuItem> <MenuItem value={2}>{LL.MQTT_NEST_2()}</MenuItem>
</ValidatedTextField> </ValidatedTextField>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="send_response" checked={data.send_response} onChange={updateFormValue} />} control={<Checkbox name="send_response" checked={data.send_response} onChange={updateFormValue} />}
label="Publish command output to a 'response' topic" label={LL.MQTT_RESPONSE()}
/> />
{!data.ha_enabled && ( {!data.ha_enabled && (
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start"> <Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item> <Grid item>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="publish_single" checked={data.publish_single} onChange={updateFormValue} />} control={<Checkbox name="publish_single" checked={data.publish_single} onChange={updateFormValue} />}
label="Publish single value topics on change" label={LL.MQTT_PUBLISH_TEXT_1()}
/> />
</Grid> </Grid>
{data.publish_single && ( {data.publish_single && (
@@ -200,7 +204,7 @@ const MqttSettingsForm: FC = () => {
control={ control={
<Checkbox name="publish_single2cmd" checked={data.publish_single2cmd} onChange={updateFormValue} /> <Checkbox name="publish_single2cmd" checked={data.publish_single2cmd} onChange={updateFormValue} />
} }
label="Publish to command topics (ioBroker)" label={LL.MQTT_PUBLISH_TEXT_2()}
/> />
</Grid> </Grid>
)} )}
@@ -211,14 +215,14 @@ const MqttSettingsForm: FC = () => {
<Grid item> <Grid item>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="ha_enabled" checked={data.ha_enabled} onChange={updateFormValue} />} control={<Checkbox name="ha_enabled" checked={data.ha_enabled} onChange={updateFormValue} />}
label="Enable MQTT Discovery (Home Assistant, Domoticz)" label={LL.MQTT_PUBLISH_TEXT_3()}
/> />
</Grid> </Grid>
{data.ha_enabled && ( {data.ha_enabled && (
<Grid item xs={6}> <Grid item xs={6}>
<ValidatedTextField <ValidatedTextField
name="discovery_prefix" name="discovery_prefix"
label="Prefix for the Discovery topics" label={LL.MQTT_PUBLISH_TEXT_4()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.discovery_prefix} value={data.discovery_prefix}
@@ -230,14 +234,14 @@ const MqttSettingsForm: FC = () => {
</Grid> </Grid>
)} )}
<Typography sx={{ pt: 2 }} variant="h6" color="primary"> <Typography sx={{ pt: 2 }} variant="h6" color="primary">
Publish Intervals (0=auto) {LL.MQTT_PUBLISH_INTERVALS()}&nbsp;(0=auto)
</Typography> </Typography>
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start"> <Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={4}> <Grid item xs={4}>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="publish_time_boiler" name="publish_time_boiler"
label="Boilers and Heat Pumps" label={LL.MQTT_INT_BOILER()}
InputProps={{ InputProps={{
endAdornment: <InputAdornment position="end">seconds</InputAdornment> endAdornment: <InputAdornment position="end">seconds</InputAdornment>
}} }}
@@ -253,7 +257,7 @@ const MqttSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="publish_time_thermostat" name="publish_time_thermostat"
label="Thermostats" label={LL.MQTT_INT_THERMOSTATS()}
InputProps={{ InputProps={{
endAdornment: <InputAdornment position="end">seconds</InputAdornment> endAdornment: <InputAdornment position="end">seconds</InputAdornment>
}} }}
@@ -269,7 +273,7 @@ const MqttSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="publish_time_solar" name="publish_time_solar"
label="Solar Modules" label={LL.MQTT_INT_SOLAR()}
InputProps={{ InputProps={{
endAdornment: <InputAdornment position="end">seconds</InputAdornment> endAdornment: <InputAdornment position="end">seconds</InputAdornment>
}} }}
@@ -285,7 +289,7 @@ const MqttSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="publish_time_mixer" name="publish_time_mixer"
label="Mixer Modules" label={LL.MQTT_INT_MIXER()}
InputProps={{ InputProps={{
endAdornment: <InputAdornment position="end">seconds</InputAdornment> endAdornment: <InputAdornment position="end">seconds</InputAdornment>
}} }}
@@ -301,7 +305,7 @@ const MqttSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="publish_time_sensor" name="publish_time_sensor"
label="Temperature Sensors" label={LL.TEMP_SENSORS()}
InputProps={{ InputProps={{
endAdornment: <InputAdornment position="end">seconds</InputAdornment> endAdornment: <InputAdornment position="end">seconds</InputAdornment>
}} }}
@@ -320,7 +324,7 @@ const MqttSettingsForm: FC = () => {
InputProps={{ InputProps={{
endAdornment: <InputAdornment position="end">seconds</InputAdornment> endAdornment: <InputAdornment position="end">seconds</InputAdornment>
}} }}
label="Default" label={LL.DEFAULT()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.publish_time_other)} value={numberValue(data.publish_time_other)}
@@ -339,7 +343,7 @@ const MqttSettingsForm: FC = () => {
type="submit" type="submit"
onClick={validateAndSubmit} onClick={validateAndSubmit}
> >
Save {LL.SAVE()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</> </>
@@ -347,7 +351,7 @@ const MqttSettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title="MQTT Settings" titleGutter> <SectionContent title={'MQTT ' + LL.SETTINGS()} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -11,6 +11,8 @@ import { MqttStatus, MqttDisconnectReason } from '../../types';
import * as MqttApi from '../../api/mqtt'; import * as MqttApi from '../../api/mqtt';
import { useRest } from '../../utils'; import { useRest } from '../../utils';
import { useI18nContext } from '../../i18n/i18n-react';
export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: Theme) => { export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: Theme) => {
if (!enabled) { if (!enabled) {
return theme.palette.info.main; return theme.palette.info.main;
@@ -29,17 +31,24 @@ export const mqttPublishHighlight = ({ mqtt_fails }: MqttStatus, theme: Theme) =
return theme.palette.error.main; return theme.palette.error.main;
}; };
export const mqttStatus = ({ enabled, connected }: MqttStatus) => { const MqttStatusForm: FC = () => {
const { loadData, data, errorMessage } = useRest<MqttStatus>({ read: MqttApi.readMqttStatus });
const { LL } = useI18nContext();
const theme = useTheme();
const mqttStatus = ({ enabled, connected }: MqttStatus) => {
if (!enabled) { if (!enabled) {
return 'Not enabled'; return LL.NOT_ENABLED();
} }
if (connected) { if (connected) {
return 'Connected'; return LL.CONNECTED();
} }
return 'Disconnected'; return LL.DISCONNECTED();
}; };
export const disconnectReason = ({ disconnect_reason }: MqttStatus) => { const disconnectReason = ({ disconnect_reason }: MqttStatus) => {
switch (disconnect_reason) { switch (disconnect_reason) {
case MqttDisconnectReason.TCP_DISCONNECTED: case MqttDisconnectReason.TCP_DISCONNECTED:
return 'TCP disconnected'; return 'TCP disconnected';
@@ -60,12 +69,7 @@ export const disconnectReason = ({ disconnect_reason }: MqttStatus) => {
default: default:
return 'Unknown'; return 'Unknown';
} }
}; };
const MqttStatusForm: FC = () => {
const { loadData, data, errorMessage } = useRest<MqttStatus>({ read: MqttApi.readMqttStatus });
const theme = useTheme();
const content = () => { const content = () => {
if (!data) { if (!data) {
@@ -89,7 +93,7 @@ const MqttStatusForm: FC = () => {
<SpeakerNotesOffIcon /> <SpeakerNotesOffIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="MQTT Publish Errors" secondary={data.mqtt_fails} /> <ListItemText primary={'MQTT Publish ' + LL.ERRORS()} secondary={data.mqtt_fails} />
</ListItem> </ListItem>
</> </>
); );
@@ -102,7 +106,7 @@ const MqttStatusForm: FC = () => {
<ReportIcon /> <ReportIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Disconnect Reason" secondary={disconnectReason(data)} /> <ListItemText primary={LL.DISCONNECT_REASON()} secondary={disconnectReason(data)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
</> </>
@@ -118,14 +122,14 @@ const MqttStatusForm: FC = () => {
<DeviceHubIcon /> <DeviceHubIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Status" secondary={mqttStatus(data)} /> <ListItemText primary={LL.STATUS()} secondary={mqttStatus(data)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
{data.enabled && renderConnectionStatus()} {data.enabled && renderConnectionStatus()}
</List> </List>
<ButtonRow> <ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}> <Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
Refresh {LL.REFRESH()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</> </>
@@ -133,7 +137,7 @@ const MqttStatusForm: FC = () => {
}; };
return ( return (
<SectionContent title="MQTT Status" titleGutter> <SectionContent title={'MQTT ' + LL.STATUS()} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -12,12 +12,16 @@ import * as NTPApi from '../../api/ntp';
import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ'; import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ';
import { NTP_SETTINGS_VALIDATOR } from '../../validators/ntp'; import { NTP_SETTINGS_VALIDATOR } from '../../validators/ntp';
import { useI18nContext } from '../../i18n/i18n-react';
const NTPSettingsForm: FC = () => { const NTPSettingsForm: FC = () => {
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<NTPSettings>({ const { loadData, saving, data, setData, saveData, errorMessage } = useRest<NTPSettings>({
read: NTPApi.readNTPSettings, read: NTPApi.readNTPSettings,
update: NTPApi.updateNTPSettings update: NTPApi.updateNTPSettings
}); });
const { LL } = useI18nContext();
const updateFormValue = updateValue(setData); const updateFormValue = updateValue(setData);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -49,7 +53,7 @@ const NTPSettingsForm: FC = () => {
<> <>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />} control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />}
label="Enable NTP" label={LL.ENABLE_NTP()}
/> />
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
@@ -64,7 +68,7 @@ const NTPSettingsForm: FC = () => {
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="tz_label" name="tz_label"
label="Time zone" label={LL.TIME_ZONE()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={selectedTimeZone(data.tz_label, data.tz_format)} value={selectedTimeZone(data.tz_label, data.tz_format)}
@@ -72,7 +76,7 @@ const NTPSettingsForm: FC = () => {
margin="normal" margin="normal"
select select
> >
<MenuItem disabled>Time zone...</MenuItem> <MenuItem disabled>{LL.TIME_ZONE()}...</MenuItem>
{timeZoneSelectItems()} {timeZoneSelectItems()}
</ValidatedTextField> </ValidatedTextField>
<ButtonRow> <ButtonRow>
@@ -84,7 +88,7 @@ const NTPSettingsForm: FC = () => {
type="submit" type="submit"
onClick={validateAndSubmit} onClick={validateAndSubmit}
> >
Save {LL.SAVE()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</> </>
@@ -92,7 +96,7 @@ const NTPSettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title="NTP Settings" titleGutter> <SectionContent title={'NTP ' + LL.SETTINGS()} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -49,19 +49,6 @@ export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => {
} }
}; };
export const ntpStatus = ({ status }: NTPStatus) => {
switch (status) {
case NTPSyncStatus.NTP_DISABLED:
return 'Disabled';
case NTPSyncStatus.NTP_INACTIVE:
return 'Inactive';
case NTPSyncStatus.NTP_ACTIVE:
return 'Active';
default:
return 'Unknown';
}
};
const NTPStatusForm: FC = () => { const NTPStatusForm: FC = () => {
const { loadData, data, errorMessage } = useRest<NTPStatus>({ read: NTPApi.readNTPStatus }); const { loadData, data, errorMessage } = useRest<NTPStatus>({ read: NTPApi.readNTPStatus });
const [localTime, setLocalTime] = useState<string>(''); const [localTime, setLocalTime] = useState<string>('');
@@ -81,6 +68,19 @@ const NTPStatusForm: FC = () => {
const theme = useTheme(); const theme = useTheme();
const ntpStatus = ({ status }: NTPStatus) => {
switch (status) {
case NTPSyncStatus.NTP_DISABLED:
return LL.DISABLED();
case NTPSyncStatus.NTP_INACTIVE:
return LL.INACTIVE();
case NTPSyncStatus.NTP_ACTIVE:
return LL.ACTIVE();
default:
return LL.UNKNOWN();
}
};
const configureTime = async () => { const configureTime = async () => {
setProcessing(true); setProcessing(true);
try { try {
@@ -100,11 +100,11 @@ const NTPStatusForm: FC = () => {
const renderSetTimeDialog = () => { const renderSetTimeDialog = () => {
return ( return (
<Dialog open={settingTime} onClose={() => setSettingTime(false)}> <Dialog open={settingTime} onClose={() => setSettingTime(false)}>
<DialogTitle>Set Time</DialogTitle> <DialogTitle>{LL.SET_TIME()}</DialogTitle>
<DialogContent dividers> <DialogContent dividers>
<Box mb={2}>Enter local date and time below to set the device's time.</Box> <Box mb={2}>{LL.SET_TIME_TEXT()}</Box>
<TextField <TextField
label="Local Time" label={LL.LOCAL_TIME()}
type="datetime-local" type="datetime-local"
value={localTime} value={localTime}
onChange={updateLocalTime} onChange={updateLocalTime}
@@ -118,7 +118,7 @@ const NTPStatusForm: FC = () => {
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setSettingTime(false)} color="secondary"> <Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setSettingTime(false)} color="secondary">
Cancel {LL.CANCEL()}
</Button> </Button>
<Button <Button
startIcon={<AccessTimeIcon />} startIcon={<AccessTimeIcon />}
@@ -128,7 +128,7 @@ const NTPStatusForm: FC = () => {
color="primary" color="primary"
autoFocus autoFocus
> >
Set Time {LL.SET_TIME()}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@@ -149,7 +149,7 @@ const NTPStatusForm: FC = () => {
<UpdateIcon /> <UpdateIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Status" secondary={ntpStatus(data)} /> <ListItemText primary={LL.STATUS()} secondary={ntpStatus(data)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
{isNtpEnabled(data) && ( {isNtpEnabled(data) && (
@@ -171,7 +171,7 @@ const NTPStatusForm: FC = () => {
<AccessTimeIcon /> <AccessTimeIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Local Time" secondary={formatDateTime(data.local_time)} /> <ListItemText primary={LL.LOCAL_TIME()} secondary={formatDateTime(data.local_time)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -180,7 +180,7 @@ const NTPStatusForm: FC = () => {
<SwapVerticalCircleIcon /> <SwapVerticalCircleIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="UTC Time" secondary={formatDateTime(data.utc_time)} /> <ListItemText primary={LL.UTC_TIME()} secondary={formatDateTime(data.utc_time)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
</List> </List>
@@ -188,7 +188,7 @@ const NTPStatusForm: FC = () => {
<Box flexGrow={1}> <Box flexGrow={1}>
<ButtonRow> <ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}> <Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
Refresh {LL.REFRESH()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</Box> </Box>
@@ -196,7 +196,7 @@ const NTPStatusForm: FC = () => {
<Box flexWrap="nowrap" whiteSpace="nowrap"> <Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow> <ButtonRow>
<Button onClick={openSetTime} variant="outlined" color="primary" startIcon={<AccessTimeIcon />}> <Button onClick={openSetTime} variant="outlined" color="primary" startIcon={<AccessTimeIcon />}>
Set Time {LL.SET_TIME()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</Box> </Box>
@@ -208,7 +208,7 @@ const NTPStatusForm: FC = () => {
}; };
return ( return (
<SectionContent title="NTP Status" titleGutter> <SectionContent title={'NTP ' + LL.STATUS()} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -13,7 +13,7 @@ import { useI18nContext } from '../../i18n/i18n-react';
const NetworkTime: FC = () => { const NetworkTime: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle(LL.NETWORK_TIME()); useLayoutTitle("NTP");
const authenticatedContext = useContext(AuthenticatedContext); const authenticatedContext = useContext(AuthenticatedContext);
const { routerTab } = useRouterTab(); const { routerTab } = useRouterTab();
@@ -21,8 +21,8 @@ const NetworkTime: FC = () => {
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="status" label="NTP Status" /> <Tab value="status" label={'NTP ' + LL.STATUS()} />
<Tab value="settings" label="NTP Settings" disabled={!authenticatedContext.me.admin} /> <Tab value="settings" label={'NTP ' + LL.SETTINGS()} disabled={!authenticatedContext.me.admin} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<NTPStatusForm />} /> <Route path="status" element={<NTPStatusForm />} />

View File

@@ -19,6 +19,8 @@ import { MessageBox } from '../../components';
import * as SecurityApi from '../../api/security'; import * as SecurityApi from '../../api/security';
import { Token } from '../../types'; import { Token } from '../../types';
import { useI18nContext } from '../../i18n/i18n-react';
interface GenerateTokenProps { interface GenerateTokenProps {
username?: string; username?: string;
onClose: () => void; onClose: () => void;
@@ -28,15 +30,17 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
const [token, setToken] = useState<Token>(); const [token, setToken] = useState<Token>();
const open = !!username; const open = !!username;
const { LL } = useI18nContext();
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const getToken = useCallback(async () => { const getToken = useCallback(async () => {
try { try {
setToken((await SecurityApi.generateToken(username)).data); setToken((await SecurityApi.generateToken(username)).data);
} catch (error: unknown) { } catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem generating token'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} }
}, [username, enqueueSnackbar]); }, [username, enqueueSnackbar, LL]);
useEffect(() => { useEffect(() => {
if (open) { if (open) {
@@ -46,16 +50,11 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
return ( return (
<Dialog onClose={onClose} aria-labelledby="generate-token-dialog-title" open={!!username} fullWidth maxWidth="sm"> <Dialog onClose={onClose} aria-labelledby="generate-token-dialog-title" open={!!username} fullWidth maxWidth="sm">
<DialogTitle id="generate-token-dialog-title">Access Token for {username}</DialogTitle> <DialogTitle id="generate-token-dialog-title">{LL.ACCESS_TOKEN_FOR() + ' ' + username}</DialogTitle>
<DialogContent dividers> <DialogContent dividers>
{token ? ( {token ? (
<> <>
<MessageBox <MessageBox message={LL.ACCESS_TOKEN_TEXT()} level="info" my={2} />
message="The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the
'Authorization' header or in the 'access_token' URL query parameter."
level="info"
my={2}
/>
<Box mt={2} mb={2}> <Box mt={2} mb={2}>
<TextField label="Token" multiline value={token.token} fullWidth contentEditable={false} /> <TextField label="Token" multiline value={token.token} fullWidth contentEditable={false} />
</Box> </Box>
@@ -63,13 +62,13 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
) : ( ) : (
<Box m={4} textAlign="center"> <Box m={4} textAlign="center">
<LinearProgress /> <LinearProgress />
<Typography variant="h6">Generating token&hellip;</Typography> <Typography variant="h6">{LL.GENERATING_TOKEN()}&hellip;</Typography>
</Box> </Box>
)} )}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button startIcon={<CloseIcon />} variant="outlined" onClick={onClose} color="secondary"> <Button startIcon={<CloseIcon />} variant="outlined" onClick={onClose} color="secondary">
Close {LL.CLOSE()}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@@ -20,6 +20,8 @@ import { createUserValidator } from '../../validators';
import { useRest } from '../../utils'; import { useRest } from '../../utils';
import { AuthenticatedContext } from '../../contexts/authentication'; import { AuthenticatedContext } from '../../contexts/authentication';
import { useI18nContext } from '../../i18n/i18n-react';
import GenerateToken from './GenerateToken'; import GenerateToken from './GenerateToken';
import UserForm from './UserForm'; import UserForm from './UserForm';
@@ -34,9 +36,11 @@ const ManageUsersForm: FC = () => {
const [generatingToken, setGeneratingToken] = useState<string>(); const [generatingToken, setGeneratingToken] = useState<string>();
const authenticatedContext = useContext(AuthenticatedContext); const authenticatedContext = useContext(AuthenticatedContext);
const { LL } = useI18nContext();
const table_theme = useTheme({ const table_theme = useTheme({
Table: ` Table: `
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 120px; --data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 120px 120px;
`, `,
BaseRow: ` BaseRow: `
font-size: 14px; font-size: 14px;
@@ -136,8 +140,8 @@ const ManageUsersForm: FC = () => {
<> <>
<Header> <Header>
<HeaderRow> <HeaderRow>
<HeaderCell resize>USERNAME</HeaderCell> <HeaderCell resize>{LL.USERNAME()}</HeaderCell>
<HeaderCell stiff>IS ADMIN</HeaderCell> <HeaderCell stiff>{LL.IS_ADMIN()}</HeaderCell>
<HeaderCell stiff /> <HeaderCell stiff />
</HeaderRow> </HeaderRow>
</Header> </Header>
@@ -169,9 +173,7 @@ const ManageUsersForm: FC = () => {
)} )}
</Table> </Table>
{noAdminConfigured() && ( {noAdminConfigured() && <MessageBox level="warning" message={LL.USER_WARNING()} my={2} />}
<MessageBox level="warning" message="You must have at least one admin user configured" my={2} />
)}
<Box display="flex" flexWrap="wrap"> <Box display="flex" flexWrap="wrap">
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}> <Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
@@ -183,14 +185,14 @@ const ManageUsersForm: FC = () => {
type="submit" type="submit"
onClick={onSubmit} onClick={onSubmit}
> >
Save {LL.SAVE()}
</Button> </Button>
</Box> </Box>
<Box flexWrap="nowrap" whiteSpace="nowrap"> <Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow> <ButtonRow>
<Button startIcon={<PersonAddIcon />} variant="outlined" color="secondary" onClick={createUser}> <Button startIcon={<PersonAddIcon />} variant="outlined" color="secondary" onClick={createUser}>
Add {LL.ADD()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</Box> </Box>
@@ -210,7 +212,7 @@ const ManageUsersForm: FC = () => {
}; };
return ( return (
<SectionContent title="Manage Users" titleGutter> <SectionContent title={LL.MANAGE_USERS()} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -19,8 +19,8 @@ const Security: FC = () => {
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="users" label="Manage Users" /> <Tab value="users" label={LL.MANAGE_USERS()} />
<Tab value="settings" label="Security Settings" /> <Tab value="settings" label={LL.SECURITY() + ' ' + LL.SETTINGS()} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="users" element={<ManageUsersForm />} /> <Route path="users" element={<ManageUsersForm />} />

View File

@@ -11,7 +11,11 @@ import { SECURITY_SETTINGS_VALIDATOR, validate } from '../../validators';
import { updateValue, useRest } from '../../utils'; import { updateValue, useRest } from '../../utils';
import { AuthenticatedContext } from '../../contexts/authentication'; import { AuthenticatedContext } from '../../contexts/authentication';
import { useI18nContext } from '../../i18n/i18n-react';
const SecuritySettingsForm: FC = () => { const SecuritySettingsForm: FC = () => {
const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<SecuritySettings>({ const { loadData, saving, data, setData, saveData, errorMessage } = useRest<SecuritySettings>({
read: SecurityApi.readSecuritySettings, read: SecurityApi.readSecuritySettings,
@@ -42,7 +46,7 @@ const SecuritySettingsForm: FC = () => {
<ValidatedPasswordField <ValidatedPasswordField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="jwt_secret" name="jwt_secret"
label="su Password" label={"su " + LL.PASSWORD()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.jwt_secret} value={data.jwt_secret}
@@ -51,7 +55,7 @@ const SecuritySettingsForm: FC = () => {
/> />
<MessageBox <MessageBox
level="info" level="info"
message="The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console." message={LL.SU_TEXT()}
mt={1} mt={1}
/> />
<ButtonRow> <ButtonRow>
@@ -63,7 +67,7 @@ const SecuritySettingsForm: FC = () => {
type="submit" type="submit"
onClick={validateAndSubmit} onClick={validateAndSubmit}
> >
Save {LL.SAVE()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</> </>
@@ -71,7 +75,7 @@ const SecuritySettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title="Security Settings" titleGutter> <SectionContent title={LL.SECURITY() + " " + LL.SETTINGS()} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -11,6 +11,8 @@ import { User } from '../../types';
import { updateValue } from '../../utils'; import { updateValue } from '../../utils';
import { validate } from '../../validators'; import { validate } from '../../validators';
import { useI18nContext } from '../../i18n/i18n-react';
interface UserFormProps { interface UserFormProps {
creating: boolean; creating: boolean;
validator: Schema; validator: Schema;
@@ -23,6 +25,8 @@ interface UserFormProps {
} }
const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDoneEditing, onCancelEditing }) => { const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDoneEditing, onCancelEditing }) => {
const { LL } = useI18nContext();
const updateFormValue = updateValue(setUser); const updateFormValue = updateValue(setUser);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const open = !!user; const open = !!user;
@@ -49,12 +53,14 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
<Dialog onClose={onCancelEditing} open={!!user} fullWidth maxWidth="sm"> <Dialog onClose={onCancelEditing} open={!!user} fullWidth maxWidth="sm">
{user && ( {user && (
<> <>
<DialogTitle id="user-form-dialog-title">{creating ? 'Add' : 'Modify'} User</DialogTitle> <DialogTitle id="user-form-dialog-title">
{creating ? LL.ADD() : LL.MODIFY()}&nbsp;{LL.USER()}
</DialogTitle>
<DialogContent dividers> <DialogContent dividers>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="username" name="username"
label="Username" label={LL.USERNAME()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={user.username} value={user.username}
@@ -65,7 +71,7 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
<ValidatedPasswordField <ValidatedPasswordField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="password" name="password"
label="Password" label={LL.PASSWORD()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={user.password} value={user.password}
@@ -74,12 +80,12 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
/> />
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="admin" checked={user.admin} onChange={updateFormValue} />} control={<Checkbox name="admin" checked={user.admin} onChange={updateFormValue} />}
label="is Admin?" label={LL.IS_ADMIN()}
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button startIcon={<CancelIcon />} variant="outlined" onClick={onCancelEditing} color="secondary"> <Button startIcon={<CancelIcon />} variant="outlined" onClick={onCancelEditing} color="secondary">
Cancel {LL.CANCEL()}
</Button> </Button>
<Button <Button
startIcon={<PersonAddIcon />} startIcon={<PersonAddIcon />}
@@ -88,7 +94,7 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
color="primary" color="primary"
autoFocus autoFocus
> >
Add {LL.ADD()}
</Button> </Button>
</DialogActions> </DialogActions>
</> </>

View File

@@ -18,7 +18,6 @@ const de: Translation = {
INVALID_LOGIN: 'Ungültige Login Daten', INVALID_LOGIN: 'Ungültige Login Daten',
NETWORK_CONNECTION: 'Netzwerkverbindung', NETWORK_CONNECTION: 'Netzwerkverbindung',
SECURITY: 'Sicherheit', SECURITY: 'Sicherheit',
NETWORK_TIME: 'Netzwerkzeit',
ONOFF_CAP: 'AN/AUS', ONOFF_CAP: 'AN/AUS',
ONOFF: 'an/aus', ONOFF: 'an/aus',
TYPE: 'Typ', TYPE: 'Typ',
@@ -173,7 +172,58 @@ const de: Translation = {
UPLOADING: 'DE_Uploading', UPLOADING: 'DE_Uploading',
UPLOAD_DROP_TEXT: 'DE_Drop file or click here', UPLOAD_DROP_TEXT: 'DE_Drop file or click here',
ERROR: 'DE_Unexpected Error, please try again', ERROR: 'DE_Unexpected Error, please try again',
TIME_SET: 'DE_Time set' TIME_SET: 'DE_Time set',
MANAGE_USERS: 'DE_Manage Users',
IS_ADMIN: 'DE_is Admin',
USER_WARNING: 'DE_You must have at least one admin user configured',
ADD: 'DE_Add',
ACCESS_TOKEN_FOR: 'DE_Access Token for',
ACCESS_TOKEN_TEXT:
'DE_The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the Authorization header or in the access_token URL query parameter.',
GENERATING_TOKEN: 'DE_Generating token',
USER: 'DE_User',
MODIFY: 'DE_Modify',
SU_TEXT:
'DE_The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console.',
NOT_ENABLED: 'DE_Not enabled',
ERRORS: 'DE_Errors',
DISCONNECT_REASON: 'DE_Disconnect Reason',
ENABLE_MQTT: 'DE_Enable MQTT',
OPTIONAL: 'DE_Optional',
FORMATTING: 'DE_Formatting',
FORMAT: 'DE_Format',
MQTT_NEST_1: 'DE_Nested in a single topic',
MQTT_NEST_2: 'DE_As individual topics',
MQTT_RESPONSE: 'DE_Publish command output to a `response` topic',
MQTT_PUBLISH_TEXT_1: 'DE_Publish single value topics on change',
MQTT_PUBLISH_TEXT_2: 'DE_Publish to command topics (ioBroker)',
MQTT_PUBLISH_TEXT_3: 'DE_Enable MQTT Discovery (Home Assistant, Domoticz)',
MQTT_PUBLISH_TEXT_4: 'DE_Prefix for the Discovery topics',
MQTT_PUBLISH_INTERVALS: 'DE_Publish Intervals',
MQTT_INT_BOILER: 'DE_Boilers and Heat Pumps',
MQTT_INT_THERMOSTATS: 'DE_Thermostats',
MQTT_INT_SOLAR: 'DE_Solar Modules',
MQTT_INT_MIXER: 'DE_Mixer Modules',
DEFAULT: 'DE_Default',
MQTT_CLEAN_SESSION: 'DE_Set Clean Session',
MQTT_RETAIN_FLAG: 'DE_Always set Retain flag',
INACTIVE: 'DE_Inactive',
ACTIVE: 'DE_Active',
UNKNOWN: 'DE_Unknown',
SET_TIME: 'DE_Set Time',
SET_TIME_TEXT: 'DE_Enter local date and time below to set the time.',
LOCAL_TIME: 'DE_Local Time',
UTC_TIME: 'DE_UTC Time',
ENABLE_NTP: 'DE_Enable NTP',
TIME_ZONE: 'DE_Time Zone',
ACCESS_POINT: 'DE_Access Point',
AP_PROVIDE: 'DE_Enable Access Point',
AP_PROVIDE_TEXT_1: 'DE_always',
AP_PROVIDE_TEXT_2: 'DE_when WiFi is disconnected',
AP_PROVIDE_TEXT_3: 'DE_never',
AP_PREFERRED_CHANNEL: 'DE_Preferred Channel',
AP_HIDE_SSID: 'DE_Hide SSID'
}; };
export default de; export default de;

View File

@@ -18,7 +18,6 @@ const en: BaseTranslation = {
INVALID_LOGIN: 'Invalid login details', INVALID_LOGIN: 'Invalid login details',
NETWORK_CONNECTION: 'Network Connection', NETWORK_CONNECTION: 'Network Connection',
SECURITY: 'Security', SECURITY: 'Security',
NETWORK_TIME: 'Network Time',
ONOFF_CAP: 'ON/OFF', ONOFF_CAP: 'ON/OFF',
ONOFF: 'on/off', ONOFF: 'on/off',
TYPE: 'Type', TYPE: 'Type',
@@ -173,7 +172,57 @@ const en: BaseTranslation = {
UPLOADING: 'Uploading', UPLOADING: 'Uploading',
UPLOAD_DROP_TEXT: 'Drop file or click here', UPLOAD_DROP_TEXT: 'Drop file or click here',
ERROR: 'Unexpected Error, please try again', ERROR: 'Unexpected Error, please try again',
TIME_SET: 'Time set' TIME_SET: 'Time set',
MANAGE_USERS: 'Manage Users',
IS_ADMIN: 'is Admin',
USER_WARNING: 'You must have at least one admin user configured',
ADD: 'Add',
ACCESS_TOKEN_FOR: 'Access Token for',
ACCESS_TOKEN_TEXT:
'The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the Authorization header or in the access_token URL query parameter.',
GENERATING_TOKEN: 'Generating token',
USER: 'User',
MODIFY: 'Modify',
SU_TEXT:
'The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console.',
NOT_ENABLED: 'Not enabled',
ERRORS: 'Errors',
DISCONNECT_REASON: 'Disconnect Reason',
ENABLE_MQTT: 'Enable MQTT',
OPTIONAL: 'Optional',
FORMATTING: 'Formatting',
FORMAT: 'Format',
MQTT_NEST_1: 'Nested in a single topic',
MQTT_NEST_2: 'As individual topics',
MQTT_RESPONSE: 'Publish command output to a `response` topic',
MQTT_PUBLISH_TEXT_1: 'Publish single value topics on change',
MQTT_PUBLISH_TEXT_2: 'Publish to command topics (ioBroker)',
MQTT_PUBLISH_TEXT_3: 'Enable MQTT Discovery (Home Assistant, Domoticz)',
MQTT_PUBLISH_TEXT_4: 'Prefix for the Discovery topics',
MQTT_PUBLISH_INTERVALS: 'Publish Intervals',
MQTT_INT_BOILER: 'Boilers and Heat Pumps',
MQTT_INT_THERMOSTATS: 'Thermostats',
MQTT_INT_SOLAR: 'Solar Modules',
MQTT_INT_MIXER: 'Mixer Modules',
DEFAULT: 'Default',
MQTT_CLEAN_SESSION: 'Set Clean Session',
MQTT_RETAIN_FLAG: 'Always set Retain flag',
INACTIVE: 'Inactive',
ACTIVE: 'Active',
UNKNOWN: 'Unknown',
SET_TIME: 'Set Time',
SET_TIME_TEXT: 'Enter local date and time below to set the time.',
LOCAL_TIME: 'Local Time',
UTC_TIME: 'UTC Time',
ENABLE_NTP: 'Enable NTP',
TIME_ZONE: 'Time Zone',
ACCESS_POINT: 'Access Point',
AP_PROVIDE: 'Enable Access Point',
AP_PROVIDE_TEXT_1: 'always',
AP_PROVIDE_TEXT_2: 'when WiFi is disconnected',
AP_PROVIDE_TEXT_3: 'never',
AP_PREFERRED_CHANNEL: 'Preferred Channel',
AP_HIDE_SSID: 'Hide SSID'
}; };
export default en; export default en;

View File

@@ -83,10 +83,6 @@ type RootTranslation = {
* Security * Security
*/ */
SECURITY: string SECURITY: string
/**
* Network Time
*/
NETWORK_TIME: string
/** /**
* ON/OFF * ON/OFF
*/ */
@@ -695,6 +691,198 @@ type RootTranslation = {
* Time set * Time set
*/ */
TIME_SET: string TIME_SET: string
/**
* Manage Users
*/
MANAGE_USERS: string
/**
* is Admin
*/
IS_ADMIN: string
/**
* You must have at least one admin user configured
*/
USER_WARNING: string
/**
* Add
*/
ADD: string
/**
* Access Token for
*/
ACCESS_TOKEN_FOR: string
/**
* The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the Authorization header or in the access_token URL query parameter.
*/
ACCESS_TOKEN_TEXT: string
/**
* Generating token
*/
GENERATING_TOKEN: string
/**
* User
*/
USER: string
/**
* Modify
*/
MODIFY: string
/**
* The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console.
*/
SU_TEXT: string
/**
* Not enabled
*/
NOT_ENABLED: string
/**
* Errors
*/
ERRORS: string
/**
* Disconnect Reason
*/
DISCONNECT_REASON: string
/**
* Enable MQTT
*/
ENABLE_MQTT: string
/**
* Optional
*/
OPTIONAL: string
/**
* Formatting
*/
FORMATTING: string
/**
* Format
*/
FORMAT: string
/**
* Nested in a single topic
*/
MQTT_NEST_1: string
/**
* As individual topics
*/
MQTT_NEST_2: string
/**
* Publish command output to a `response` topic
*/
MQTT_RESPONSE: string
/**
* Publish single value topics on change
*/
MQTT_PUBLISH_TEXT_1: string
/**
* Publish to command topics (ioBroker)
*/
MQTT_PUBLISH_TEXT_2: string
/**
* Enable MQTT Discovery (Home Assistant, Domoticz)
*/
MQTT_PUBLISH_TEXT_3: string
/**
* Prefix for the Discovery topics
*/
MQTT_PUBLISH_TEXT_4: string
/**
* Publish Intervals
*/
MQTT_PUBLISH_INTERVALS: string
/**
* Boilers and Heat Pumps
*/
MQTT_INT_BOILER: string
/**
* Thermostats
*/
MQTT_INT_THERMOSTATS: string
/**
* Solar Modules
*/
MQTT_INT_SOLAR: string
/**
* Mixer Modules
*/
MQTT_INT_MIXER: string
/**
* Default
*/
DEFAULT: string
/**
* Set Clean Session
*/
MQTT_CLEAN_SESSION: string
/**
* Always set Retain flag
*/
MQTT_RETAIN_FLAG: string
/**
* Inactive
*/
INACTIVE: string
/**
* Active
*/
ACTIVE: string
/**
* Unknown
*/
UNKNOWN: string
/**
* Set Time
*/
SET_TIME: string
/**
* Enter local date and time below to set the time.
*/
SET_TIME_TEXT: string
/**
* Local Time
*/
LOCAL_TIME: string
/**
* UTC Time
*/
UTC_TIME: string
/**
* Enable NTP
*/
ENABLE_NTP: string
/**
* Time Zone
*/
TIME_ZONE: string
/**
* Access Point
*/
ACCESS_POINT: string
/**
* Enable Access Point
*/
AP_PROVIDE: string
/**
* always
*/
AP_PROVIDE_TEXT_1: string
/**
* when WiFi is disconnected
*/
AP_PROVIDE_TEXT_2: string
/**
* never
*/
AP_PROVIDE_TEXT_3: string
/**
* Preferred Channel
*/
AP_PREFERRED_CHANNEL: string
/**
* Hide SSID
*/
AP_HIDE_SSID: string
} }
export type TranslationFunctions = { export type TranslationFunctions = {
@@ -766,10 +954,6 @@ export type TranslationFunctions = {
* Security * Security
*/ */
SECURITY: () => LocalizedString SECURITY: () => LocalizedString
/**
* Network Time
*/
NETWORK_TIME: () => LocalizedString
/** /**
* ON/OFF * ON/OFF
*/ */
@@ -1368,6 +1552,198 @@ export type TranslationFunctions = {
* Time set * Time set
*/ */
TIME_SET: () => LocalizedString TIME_SET: () => LocalizedString
/**
* Manage Users
*/
MANAGE_USERS: () => LocalizedString
/**
* is Admin
*/
IS_ADMIN: () => LocalizedString
/**
* You must have at least one admin user configured
*/
USER_WARNING: () => LocalizedString
/**
* Add
*/
ADD: () => LocalizedString
/**
* Access Token for
*/
ACCESS_TOKEN_FOR: () => LocalizedString
/**
* The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the Authorization header or in the access_token URL query parameter.
*/
ACCESS_TOKEN_TEXT: () => LocalizedString
/**
* Generating token
*/
GENERATING_TOKEN: () => LocalizedString
/**
* User
*/
USER: () => LocalizedString
/**
* Modify
*/
MODIFY: () => LocalizedString
/**
* The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console.
*/
SU_TEXT: () => LocalizedString
/**
* Not enabled
*/
NOT_ENABLED: () => LocalizedString
/**
* Errors
*/
ERRORS: () => LocalizedString
/**
* Disconnect Reason
*/
DISCONNECT_REASON: () => LocalizedString
/**
* Enable MQTT
*/
ENABLE_MQTT: () => LocalizedString
/**
* Optional
*/
OPTIONAL: () => LocalizedString
/**
* Formatting
*/
FORMATTING: () => LocalizedString
/**
* Format
*/
FORMAT: () => LocalizedString
/**
* Nested in a single topic
*/
MQTT_NEST_1: () => LocalizedString
/**
* As individual topics
*/
MQTT_NEST_2: () => LocalizedString
/**
* Publish command output to a `response` topic
*/
MQTT_RESPONSE: () => LocalizedString
/**
* Publish single value topics on change
*/
MQTT_PUBLISH_TEXT_1: () => LocalizedString
/**
* Publish to command topics (ioBroker)
*/
MQTT_PUBLISH_TEXT_2: () => LocalizedString
/**
* Enable MQTT Discovery (Home Assistant, Domoticz)
*/
MQTT_PUBLISH_TEXT_3: () => LocalizedString
/**
* Prefix for the Discovery topics
*/
MQTT_PUBLISH_TEXT_4: () => LocalizedString
/**
* Publish Intervals
*/
MQTT_PUBLISH_INTERVALS: () => LocalizedString
/**
* Boilers and Heat Pumps
*/
MQTT_INT_BOILER: () => LocalizedString
/**
* Thermostats
*/
MQTT_INT_THERMOSTATS: () => LocalizedString
/**
* Solar Modules
*/
MQTT_INT_SOLAR: () => LocalizedString
/**
* Mixer Modules
*/
MQTT_INT_MIXER: () => LocalizedString
/**
* Default
*/
DEFAULT: () => LocalizedString
/**
* Set Clean Session
*/
MQTT_CLEAN_SESSION: () => LocalizedString
/**
* Always set Retain flag
*/
MQTT_RETAIN_FLAG: () => LocalizedString
/**
* Inactive
*/
INACTIVE: () => LocalizedString
/**
* Active
*/
ACTIVE: () => LocalizedString
/**
* Unknown
*/
UNKNOWN: () => LocalizedString
/**
* Set Time
*/
SET_TIME: () => LocalizedString
/**
* Enter local date and time below to set the time.
*/
SET_TIME_TEXT: () => LocalizedString
/**
* Local Time
*/
LOCAL_TIME: () => LocalizedString
/**
* UTC Time
*/
UTC_TIME: () => LocalizedString
/**
* Enable NTP
*/
ENABLE_NTP: () => LocalizedString
/**
* Time Zone
*/
TIME_ZONE: () => LocalizedString
/**
* Access Point
*/
ACCESS_POINT: () => LocalizedString
/**
* Enable Access Point
*/
AP_PROVIDE: () => LocalizedString
/**
* always
*/
AP_PROVIDE_TEXT_1: () => LocalizedString
/**
* when WiFi is disconnected
*/
AP_PROVIDE_TEXT_2: () => LocalizedString
/**
* never
*/
AP_PROVIDE_TEXT_3: () => LocalizedString
/**
* Preferred Channel
*/
AP_PREFERRED_CHANNEL: () => LocalizedString
/**
* Hide SSID
*/
AP_HIDE_SSID: () => LocalizedString
} }
export type Formatters = {} export type Formatters = {}