Internationalization (i18n) (#627)
* `feat: added internationalization lib, added english labels` * `fixes from code review` * `feat: add crowdin github actions` * `fix: added support for i18n in testing. Fixed broken tests` * `fix: missing translations` * `removed unneded import` * `more components updated with missing translations` * `fixed lint issue` * `Refactor: updated how translations are scoped, updated all references` * `fixing broken tests` * `feat: added language switcher, updated some translations` * `fixed linting issues` * `reverting vite config` * `add english group id's to command palette` * `updated PR template for i18n`
This commit is contained in:
@@ -5,6 +5,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
|
||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||
import { Protobuf } from "@meshtastic/core";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const Bluetooth = () => {
|
||||
const { config, setWorkingConfig } = useDevice();
|
||||
@@ -16,6 +17,7 @@ export const Bluetooth = () => {
|
||||
removeError,
|
||||
clearErrors,
|
||||
} = useAppStore();
|
||||
const { t } = useTranslation("deviceConfig");
|
||||
|
||||
const [bluetoothPin, setBluetoothPin] = useState(
|
||||
config?.bluetooth?.fixedPin.toString() ?? "",
|
||||
@@ -24,7 +26,7 @@ export const Bluetooth = () => {
|
||||
const validateBluetoothPin = (pin: string) => {
|
||||
// if empty show error they need a pin set
|
||||
if (pin === "") {
|
||||
return addError("fixedPin", "Bluetooth Pin is required");
|
||||
return addError("fixedPin", t("bluetooth.validation.pinRequired"));
|
||||
}
|
||||
|
||||
// clear any existing errors
|
||||
@@ -32,11 +34,14 @@ export const Bluetooth = () => {
|
||||
|
||||
// if it starts with 0 show error
|
||||
if (pin[0] === "0") {
|
||||
return addError("fixedPin", "Bluetooth Pin cannot start with 0");
|
||||
return addError(
|
||||
"fixedPin",
|
||||
t("bluetooth.validation.pinCannotStartWithZero"),
|
||||
);
|
||||
}
|
||||
// if it's not 6 digits show error
|
||||
if (pin.length < 6) {
|
||||
return addError("fixedPin", "Pin must be 6 digits");
|
||||
return addError("fixedPin", t("bluetooth.validation.pinMustBeSixDigits"));
|
||||
}
|
||||
|
||||
removeError("fixedPin");
|
||||
@@ -69,22 +74,21 @@ export const Bluetooth = () => {
|
||||
defaultValues={config.bluetooth}
|
||||
fieldGroups={[
|
||||
{
|
||||
label: "Bluetooth Settings",
|
||||
description: "Settings for the Bluetooth module ",
|
||||
notes:
|
||||
"Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.",
|
||||
label: t("bluetooth.title"),
|
||||
description: t("bluetooth.description"),
|
||||
notes: t("bluetooth.note"),
|
||||
fields: [
|
||||
{
|
||||
type: "toggle",
|
||||
name: "enabled",
|
||||
label: "Enabled",
|
||||
description: "Enable or disable Bluetooth",
|
||||
label: t("bluetooth.enabled.label"),
|
||||
description: t("bluetooth.enabled.description"),
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
name: "mode",
|
||||
label: "Pairing mode",
|
||||
description: "Pin selection behaviour.",
|
||||
label: t("bluetooth.pairingMode.label"),
|
||||
description: t("bluetooth.pairingMode.description"),
|
||||
selectChange: (e) => {
|
||||
if (e !== "1") {
|
||||
setBluetoothPin("");
|
||||
@@ -104,8 +108,8 @@ export const Bluetooth = () => {
|
||||
{
|
||||
type: "number",
|
||||
name: "fixedPin",
|
||||
label: "Pin",
|
||||
description: "Pin to use when pairing",
|
||||
label: t("bluetooth.pin.label"),
|
||||
description: t("bluetooth.pin.description"),
|
||||
validationText: hasFieldError("fixedPin")
|
||||
? getErrorMessage("fixedPin")
|
||||
: "",
|
||||
|
||||
@@ -4,9 +4,11 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
|
||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||
import { Protobuf } from "@meshtastic/core";
|
||||
import { useUnsafeRolesDialog } from "@components/Dialog/UnsafeRolesDialog/useUnsafeRolesDialog.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const Device = () => {
|
||||
const { config, setWorkingConfig } = useDevice();
|
||||
const { t } = useTranslation("deviceConfig");
|
||||
const { validateRoleSelection } = useUnsafeRolesDialog();
|
||||
|
||||
const onSubmit = (data: DeviceValidation) => {
|
||||
@@ -25,14 +27,14 @@ export const Device = () => {
|
||||
defaultValues={config.device}
|
||||
fieldGroups={[
|
||||
{
|
||||
label: "Device Settings",
|
||||
description: "Settings for the device",
|
||||
label: t("device.title"),
|
||||
description: t("device.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "select",
|
||||
name: "role",
|
||||
label: "Role",
|
||||
description: "What role the device performs on the mesh",
|
||||
label: t("device.role.label"),
|
||||
description: t("device.role.description"),
|
||||
validate: validateRoleSelection,
|
||||
properties: {
|
||||
enumValue: Protobuf.Config.Config_DeviceConfig_Role,
|
||||
@@ -42,20 +44,20 @@ export const Device = () => {
|
||||
{
|
||||
type: "number",
|
||||
name: "buttonGpio",
|
||||
label: "Button Pin",
|
||||
description: "Button pin override",
|
||||
label: t("device.buttonPin.label"),
|
||||
description: t("device.buttonPin.description"),
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "buzzerGpio",
|
||||
label: "Buzzer Pin",
|
||||
description: "Buzzer pin override",
|
||||
label: t("device.buzzerPin.label"),
|
||||
description: t("device.buzzerPin.description"),
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
name: "rebroadcastMode",
|
||||
label: "Rebroadcast Mode",
|
||||
description: "How to handle rebroadcasting",
|
||||
label: t("device.rebroadcastMode.label"),
|
||||
description: t("device.rebroadcastMode.description"),
|
||||
properties: {
|
||||
enumValue: Protobuf.Config.Config_DeviceConfig_RebroadcastMode,
|
||||
formatEnumName: true,
|
||||
@@ -64,29 +66,29 @@ export const Device = () => {
|
||||
{
|
||||
type: "number",
|
||||
name: "nodeInfoBroadcastSecs",
|
||||
label: "Node Info Broadcast Interval",
|
||||
description: "How often to broadcast node info",
|
||||
label: t("device.nodeInfoBroadcastInterval.label"),
|
||||
description: t("device.nodeInfoBroadcastInterval.description"),
|
||||
properties: {
|
||||
suffix: "Seconds",
|
||||
suffix: t("unit.second.plural"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
name: "doubleTapAsButtonPress",
|
||||
label: "Double Tap as Button Press",
|
||||
description: "Treat double tap as button press",
|
||||
label: t("device.doubleTapAsButtonPress.label"),
|
||||
description: t("device.doubleTapAsButtonPress.description"),
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
name: "disableTripleClick",
|
||||
label: "Disable Triple Click",
|
||||
description: "Disable triple click",
|
||||
label: t("device.disableTripleClick.label"),
|
||||
description: t("device.disableTripleClick.description"),
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "tzdef",
|
||||
label: "POSIX Timezone",
|
||||
description: "The POSIX timezone string for the device",
|
||||
label: t("device.posixTimezone.label"),
|
||||
description: t("device.posixTimezone.description"),
|
||||
properties: {
|
||||
fieldLength: {
|
||||
max: 64,
|
||||
@@ -98,8 +100,8 @@ export const Device = () => {
|
||||
{
|
||||
type: "toggle",
|
||||
name: "ledHeartbeatDisabled",
|
||||
label: "LED Heartbeat Disabled",
|
||||
description: "Disable default blinking LED",
|
||||
label: t("device.ledHeartbeatDisabled.label"),
|
||||
description: t("device.ledHeartbeatDisabled.description"),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import type { DisplayValidation } from "@app/validation/config/display.tsx";
|
||||
import type { DisplayValidation } from "@app/validation/config/display.ts";
|
||||
import { create } from "@bufbuild/protobuf";
|
||||
import { DynamicForm } from "@components/Form/DynamicForm.tsx";
|
||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||
import { Protobuf } from "@meshtastic/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const Display = () => {
|
||||
const { config, setWorkingConfig } = useDevice();
|
||||
const { t } = useTranslation("deviceConfig");
|
||||
|
||||
const onSubmit = (data: DisplayValidation) => {
|
||||
setWorkingConfig(
|
||||
@@ -24,23 +26,23 @@ export const Display = () => {
|
||||
defaultValues={config.display}
|
||||
fieldGroups={[
|
||||
{
|
||||
label: "Display Settings",
|
||||
description: "Settings for the device display",
|
||||
label: t("display.title"),
|
||||
description: t("display.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "number",
|
||||
name: "screenOnSecs",
|
||||
label: "Screen Timeout",
|
||||
description: "Turn off the display after this long",
|
||||
label: t("display.screenTimeout.label"),
|
||||
description: t("display.screenTimeout.description"),
|
||||
properties: {
|
||||
suffix: "Seconds",
|
||||
suffix: t("unit.second.plural"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
name: "gpsFormat",
|
||||
label: "GPS Display Units",
|
||||
description: "Coordinate display format",
|
||||
label: t("display.gpsDisplayUnits.label"),
|
||||
description: t("display.gpsDisplayUnits.description"),
|
||||
properties: {
|
||||
enumValue:
|
||||
Protobuf.Config.Config_DisplayConfig_GpsCoordinateFormat,
|
||||
@@ -49,35 +51,35 @@ export const Display = () => {
|
||||
{
|
||||
type: "number",
|
||||
name: "autoScreenCarouselSecs",
|
||||
label: "Carousel Delay",
|
||||
description: "How fast to cycle through windows",
|
||||
label: t("display.carouselDelay.label"),
|
||||
description: t("display.carouselDelay.description"),
|
||||
properties: {
|
||||
suffix: "Seconds",
|
||||
suffix: t("unit.second.plural"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
name: "compassNorthTop",
|
||||
label: "Compass North Top",
|
||||
description: "Fix north to the top of compass",
|
||||
label: t("display.compassNorthTop.label"),
|
||||
description: t("display.compassNorthTop.description"),
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
name: "use12hClock",
|
||||
label: "12-Hour Clock",
|
||||
description: "Use 12-hour clock format",
|
||||
label: t("display.twelveHourClock.label"),
|
||||
description: t("display.twelveHourClock.description"),
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
name: "flipScreen",
|
||||
label: "Flip Screen",
|
||||
description: "Flip display 180 degrees",
|
||||
label: t("display.flipScreen.label"),
|
||||
description: t("display.flipScreen.description"),
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
name: "units",
|
||||
label: "Display Units",
|
||||
description: "Display metric or imperial units",
|
||||
label: t("display.displayUnits.label"),
|
||||
description: t("display.displayUnits.description"),
|
||||
properties: {
|
||||
enumValue: Protobuf.Config.Config_DisplayConfig_DisplayUnits,
|
||||
formatEnumName: true,
|
||||
@@ -86,17 +88,17 @@ export const Display = () => {
|
||||
{
|
||||
type: "select",
|
||||
name: "oled",
|
||||
label: "OLED Type",
|
||||
description: "Type of OLED screen attached to the device",
|
||||
label: t("display.oledType.label"),
|
||||
description: t("display.oledType.description"),
|
||||
properties: {
|
||||
enumValue: Protobuf.Config.Config_DisplayConfig_OledType,
|
||||
enumValue: Protobuf.Config.Config_Displayjonfig_OledType,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
name: "displaymode",
|
||||
label: "Display Mode",
|
||||
description: "Screen layout variant",
|
||||
label: t("display.displayMode.label"),
|
||||
description: t("display.displayMode.description"),
|
||||
properties: {
|
||||
enumValue: Protobuf.Config.Config_DisplayConfig_DisplayMode,
|
||||
formatEnumName: true,
|
||||
@@ -105,14 +107,14 @@ export const Display = () => {
|
||||
{
|
||||
type: "toggle",
|
||||
name: "headingBold",
|
||||
label: "Bold Heading",
|
||||
description: "Bolden the heading text",
|
||||
label: t("display.headingBold.label"),
|
||||
description: t("display.headingBold.description"),
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
name: "wakeOnTapOrMotion",
|
||||
label: "Wake on Tap or Motion",
|
||||
description: "Wake the device on tap or motion",
|
||||
label: t("display.wakeOnTapOrMotion.label"),
|
||||
description: t("display.wakeOnTapOrMotion.description"),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import type { LoRaValidation } from "@app/validation/config/lora.tsx";
|
||||
import type { LoRaValidation } from "@app/validation/config/lora.ts";
|
||||
import { create } from "@bufbuild/protobuf";
|
||||
import { DynamicForm } from "@components/Form/DynamicForm.tsx";
|
||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||
import { Protobuf } from "@meshtastic/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const LoRa = () => {
|
||||
const { config, setWorkingConfig } = useDevice();
|
||||
const { t } = useTranslation("deviceConfig");
|
||||
|
||||
const onSubmit = (data: LoRaValidation) => {
|
||||
setWorkingConfig(
|
||||
@@ -24,14 +26,14 @@ export const LoRa = () => {
|
||||
defaultValues={config.lora}
|
||||
fieldGroups={[
|
||||
{
|
||||
label: "Mesh Settings",
|
||||
description: "Settings for the LoRa mesh",
|
||||
label: t("lora.title"),
|
||||
description: t("lora.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "select",
|
||||
name: "region",
|
||||
label: "Region",
|
||||
description: "Sets the region for your node",
|
||||
label: t("lora.region.label"),
|
||||
description: t("lora.region.description"),
|
||||
properties: {
|
||||
enumValue: Protobuf.Config.Config_LoRaConfig_RegionCode,
|
||||
},
|
||||
@@ -39,8 +41,8 @@ export const LoRa = () => {
|
||||
{
|
||||
type: "select",
|
||||
name: "hopLimit",
|
||||
label: "Hop Limit",
|
||||
description: "Maximum number of hops",
|
||||
label: t("lora.hopLimit.label"),
|
||||
description: t("lora.hopLimit.description"),
|
||||
properties: {
|
||||
enumValue: { 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7 },
|
||||
},
|
||||
@@ -48,39 +50,38 @@ export const LoRa = () => {
|
||||
{
|
||||
type: "number",
|
||||
name: "channelNum",
|
||||
label: "Frequency Slot",
|
||||
description: "LoRa frequency channel number",
|
||||
label: t("lora.frequencySlot.label"),
|
||||
description: t("lora.frequencySlot.description"),
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
name: "ignoreMqtt",
|
||||
label: "Ignore MQTT",
|
||||
description: "Don't forward MQTT messages over the mesh",
|
||||
label: t("lora.ignoreMqtt.label"),
|
||||
description: t("lora.ignoreMqtt.description"),
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
name: "configOkToMqtt",
|
||||
label: "OK to MQTT",
|
||||
description:
|
||||
"When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT",
|
||||
label: t("lora.okToMqtt.label"),
|
||||
description: t("lora.okToMqtt.description"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Waveform Settings",
|
||||
description: "Settings for the LoRa waveform",
|
||||
label: t("lora.waveformSettings.label"),
|
||||
description: t("lora.waveformSettings.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "toggle",
|
||||
name: "usePreset",
|
||||
label: "Use Preset",
|
||||
description: "Use one of the predefined modem presets",
|
||||
label: t("lora.usePreset.label"),
|
||||
description: t("lora.usePreset.description"),
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
name: "modemPreset",
|
||||
label: "Modem Preset",
|
||||
description: "Modem preset to use",
|
||||
label: t("lora.modemPreset.label"),
|
||||
description: t("lora.modemPreset.description"),
|
||||
disabledBy: [
|
||||
{
|
||||
fieldName: "usePreset",
|
||||
@@ -94,8 +95,8 @@ export const LoRa = () => {
|
||||
{
|
||||
type: "number",
|
||||
name: "bandwidth",
|
||||
label: "Bandwidth",
|
||||
description: "Channel bandwidth in MHz",
|
||||
label: t("lora.bandwidth.label"),
|
||||
description: t("lora.bandwidth.description"),
|
||||
disabledBy: [
|
||||
{
|
||||
fieldName: "usePreset",
|
||||
@@ -103,14 +104,14 @@ export const LoRa = () => {
|
||||
},
|
||||
],
|
||||
properties: {
|
||||
suffix: "MHz",
|
||||
suffix: t("unit.megahertz"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "spreadFactor",
|
||||
label: "Spreading Factor",
|
||||
description: "Indicates the number of chirps per symbol",
|
||||
label: t("lora.spreadingFactor.label"),
|
||||
description: t("lora.spreadingFactor.description"),
|
||||
|
||||
disabledBy: [
|
||||
{
|
||||
@@ -119,14 +120,14 @@ export const LoRa = () => {
|
||||
},
|
||||
],
|
||||
properties: {
|
||||
suffix: "CPS",
|
||||
suffix: t("unit.cps"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "codingRate",
|
||||
label: "Coding Rate",
|
||||
description: "The denominator of the coding rate",
|
||||
label: t("lora.codingRate.label"),
|
||||
description: t("lora.codingRate.description"),
|
||||
disabledBy: [
|
||||
{
|
||||
fieldName: "usePreset",
|
||||
@@ -137,53 +138,52 @@ export const LoRa = () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Radio Settings",
|
||||
description: "Settings for the LoRa radio",
|
||||
label: t("lora.radioSettings.label"),
|
||||
description: t("lora.radioSettings.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "toggle",
|
||||
name: "txEnabled",
|
||||
label: "Transmit Enabled",
|
||||
description: "Enable/Disable transmit (TX) from the LoRa radio",
|
||||
label: t("lora.transmitEnabled.label"),
|
||||
description: t("lora.transmitEnabled.description"),
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "txPower",
|
||||
label: "Transmit Power",
|
||||
description: "Max transmit power",
|
||||
label: t("lora.transmitPower.label"),
|
||||
description: t("lora.transmitPower.description"),
|
||||
properties: {
|
||||
suffix: "dBm",
|
||||
suffix: t("unit.dbm"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
name: "overrideDutyCycle",
|
||||
label: "Override Duty Cycle",
|
||||
description: "Override Duty Cycle",
|
||||
label: t("lora.overrideDutyCycle.label"),
|
||||
description: t("lora.overrideDutyCycle.description"),
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "frequencyOffset",
|
||||
label: "Frequency Offset",
|
||||
description:
|
||||
"Frequency offset to correct for crystal calibration errors",
|
||||
label: t("lora.frequencyOffset.label"),
|
||||
description: t("lora.frequencyOffset.description"),
|
||||
properties: {
|
||||
suffix: "Hz",
|
||||
suffix: t("unit.hertz"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
name: "sx126xRxBoostedGain",
|
||||
label: "Boosted RX Gain",
|
||||
description: "Boosted RX gain",
|
||||
label: t("lora.boostedRxGain.label"),
|
||||
description: t("lora.boostedRxGain.description"),
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "overrideFrequency",
|
||||
label: "Override Frequency",
|
||||
description: "Override frequency",
|
||||
label: t("lora.overrideFrequency.label"),
|
||||
description: t("lora.overrideFrequency.description"),
|
||||
properties: {
|
||||
suffix: "MHz",
|
||||
suffix: t("unit.megahertz"),
|
||||
step: 0.001,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -11,9 +11,11 @@ import {
|
||||
} from "@core/utils/ip.ts";
|
||||
import { Protobuf } from "@meshtastic/core";
|
||||
import { validateSchema } from "@app/validation/validate.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const Network = () => {
|
||||
const { config, setWorkingConfig } = useDevice();
|
||||
const { t } = useTranslation("deviceConfig");
|
||||
|
||||
const onSubmit = (data: NetworkValidation) => {
|
||||
const result = validateSchema(NetworkValidationSchema, data);
|
||||
@@ -63,22 +65,21 @@ export const Network = () => {
|
||||
}}
|
||||
fieldGroups={[
|
||||
{
|
||||
label: "WiFi Config",
|
||||
description: "WiFi radio configuration",
|
||||
notes:
|
||||
"Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.",
|
||||
label: t("network.title"),
|
||||
description: t("network.description"),
|
||||
notes: t("network.note"),
|
||||
fields: [
|
||||
{
|
||||
type: "toggle",
|
||||
name: "wifiEnabled",
|
||||
label: "Enabled",
|
||||
description: "Enable or disable the WiFi radio",
|
||||
label: t("network.wifiEnabled.label"),
|
||||
description: t("network.wifiEnabled.description"),
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "wifiSsid",
|
||||
label: "SSID",
|
||||
description: "Network name",
|
||||
label: t("network.ssid.label"),
|
||||
description: t("network.ssid.label"),
|
||||
disabledBy: [
|
||||
{
|
||||
fieldName: "wifiEnabled",
|
||||
@@ -88,8 +89,8 @@ export const Network = () => {
|
||||
{
|
||||
type: "password",
|
||||
name: "wifiPsk",
|
||||
label: "PSK",
|
||||
description: "Network password",
|
||||
label: t("network.psk.label"),
|
||||
description: t("network.psk.description"),
|
||||
disabledBy: [
|
||||
{
|
||||
fieldName: "wifiEnabled",
|
||||
@@ -99,26 +100,26 @@ export const Network = () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Ethernet Config",
|
||||
description: "Ethernet port configuration",
|
||||
label: t("network.ethernetConfigSettings.label"),
|
||||
description: t("network.ethernetConfigSettings.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "toggle",
|
||||
name: "ethEnabled",
|
||||
label: "Enabled",
|
||||
description: "Enable or disable the Ethernet port",
|
||||
label: t("network.ethernetEnabled.label"),
|
||||
description: t("network.ethernetEnabled.description"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "IP Config",
|
||||
description: "IP configuration",
|
||||
label: t("network.ipConfigSettings.label"),
|
||||
description: t("network.ipConfigSettings.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "select",
|
||||
name: "addressMode",
|
||||
label: "Address Mode",
|
||||
description: "Address assignment selection",
|
||||
label: t("network.addressMode.label"),
|
||||
description: t("network.addressMode.description"),
|
||||
properties: {
|
||||
enumValue: Protobuf.Config.Config_NetworkConfig_AddressMode,
|
||||
},
|
||||
@@ -126,8 +127,8 @@ export const Network = () => {
|
||||
{
|
||||
type: "text",
|
||||
name: "ipv4Config.ip",
|
||||
label: "IP",
|
||||
description: "IP Address",
|
||||
label: t("network.ip.label"),
|
||||
description: t("network.ip.description"),
|
||||
disabledBy: [
|
||||
{
|
||||
fieldName: "addressMode",
|
||||
@@ -139,8 +140,8 @@ export const Network = () => {
|
||||
{
|
||||
type: "text",
|
||||
name: "ipv4Config.gateway",
|
||||
label: "Gateway",
|
||||
description: "Default Gateway",
|
||||
label: t("network.gateway.label"),
|
||||
description: t("network.gateway.description"),
|
||||
disabledBy: [
|
||||
{
|
||||
fieldName: "addressMode",
|
||||
@@ -152,8 +153,8 @@ export const Network = () => {
|
||||
{
|
||||
type: "text",
|
||||
name: "ipv4Config.subnet",
|
||||
label: "Subnet",
|
||||
description: "Subnet Mask",
|
||||
label: t("network.subnet.label"),
|
||||
description: t("network.subnet.description"),
|
||||
disabledBy: [
|
||||
{
|
||||
fieldName: "addressMode",
|
||||
@@ -165,8 +166,8 @@ export const Network = () => {
|
||||
{
|
||||
type: "text",
|
||||
name: "ipv4Config.dns",
|
||||
label: "DNS",
|
||||
description: "DNS Server",
|
||||
label: t("network.dns.label"),
|
||||
description: t("network.dns.description"),
|
||||
disabledBy: [
|
||||
{
|
||||
fieldName: "addressMode",
|
||||
@@ -178,13 +179,13 @@ export const Network = () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "UDP Config",
|
||||
description: "UDP over Mesh configuration",
|
||||
label: t("network.udpConfigSettings.label"),
|
||||
description: t("network.udpConfigSettings.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "select",
|
||||
name: "enabledProtocols",
|
||||
label: "Mesh via UDP",
|
||||
label: t("network.meshViaUdp.label"),
|
||||
properties: {
|
||||
enumValue: Protobuf.Config.Config_NetworkConfig_ProtocolFlags,
|
||||
formatEnumName: true,
|
||||
@@ -193,24 +194,24 @@ export const Network = () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "NTP Config",
|
||||
description: "NTP configuration",
|
||||
label: t("network.ntpConfigSettings.label"),
|
||||
description: t("network.ntpConfigSettings.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "text",
|
||||
name: "ntpServer",
|
||||
label: "NTP Server",
|
||||
label: t("network.ntpServer.label"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Rsyslog Config",
|
||||
description: "Rsyslog configuration",
|
||||
label: t("network.rsyslogConfigSettings.label"),
|
||||
description: t("network.rsyslogConfigSettings.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "text",
|
||||
name: "rsyslogServer",
|
||||
label: "Rsyslog Server",
|
||||
label: t("network.rsyslogServer.label"),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -8,12 +8,14 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
|
||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||
import { Protobuf } from "@meshtastic/core";
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const Position = () => {
|
||||
const { config, setWorkingConfig } = useDevice();
|
||||
const { flagsValue, activeFlags, toggleFlag, getAllFlags } = usePositionFlags(
|
||||
config?.position?.positionFlags ?? 0,
|
||||
);
|
||||
const { t } = useTranslation("deviceConfig");
|
||||
|
||||
const onSubmit = (data: PositionValidation) => {
|
||||
return setWorkingConfig(
|
||||
@@ -42,22 +44,20 @@ export const Position = () => {
|
||||
defaultValues={config.position}
|
||||
fieldGroups={[
|
||||
{
|
||||
label: "Position Settings",
|
||||
description: "Settings for the position module",
|
||||
label: t("position.title"),
|
||||
description: t("position.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "toggle",
|
||||
name: "positionBroadcastSmartEnabled",
|
||||
label: "Enable Smart Position",
|
||||
description:
|
||||
"Only send position when there has been a meaningful change in location",
|
||||
label: t("position.smartPositionEnabled.label"),
|
||||
description: t("position.smartPositionEnabled.description"),
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
name: "gpsMode",
|
||||
label: "GPS Mode",
|
||||
description:
|
||||
"Configure whether device GPS is Enabled, Disabled, or Not Present",
|
||||
label: t("position.gpsMode.label"),
|
||||
description: t("position.gpsMode.description"),
|
||||
properties: {
|
||||
enumValue: Protobuf.Config.Config_PositionConfig_GpsMode,
|
||||
},
|
||||
@@ -65,9 +65,8 @@ export const Position = () => {
|
||||
{
|
||||
type: "toggle",
|
||||
name: "fixedPosition",
|
||||
label: "Fixed Position",
|
||||
description:
|
||||
"Don't report GPS position, but a manually-specified one",
|
||||
label: t("position.fixedPosition.label"),
|
||||
description: t("position.fixedPosition.description"),
|
||||
},
|
||||
{
|
||||
type: "multiSelect",
|
||||
@@ -76,10 +75,9 @@ export const Position = () => {
|
||||
isChecked: (name: string) =>
|
||||
activeFlags?.includes(name as FlagName) ?? false,
|
||||
onValueChange: onPositonFlagChange,
|
||||
label: "Position Flags",
|
||||
placeholder: "Select position flags...",
|
||||
description:
|
||||
"Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.",
|
||||
label: t("position.positionFlags.label"),
|
||||
placeholder: t("position.flags.placeholder"),
|
||||
description: t("position.positionFlags.description"),
|
||||
properties: {
|
||||
enumValue: getAllFlags(),
|
||||
},
|
||||
@@ -87,51 +85,50 @@ export const Position = () => {
|
||||
{
|
||||
type: "number",
|
||||
name: "rxGpio",
|
||||
label: "Receive Pin",
|
||||
description: "GPS module RX pin override",
|
||||
label: t("position.receivePin.label"),
|
||||
description: t("position.receivePin.description"),
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "txGpio",
|
||||
label: "Transmit Pin",
|
||||
description: "GPS module TX pin override",
|
||||
label: t("position.transmitPin.label"),
|
||||
description: t("position.transmitPin.description"),
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "gpsEnGpio",
|
||||
label: "Enable Pin",
|
||||
description: "GPS module enable pin override",
|
||||
label: t("position.enablePin.label"),
|
||||
description: t("position.enablePin.description"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Intervals",
|
||||
description: "How often to send position updates",
|
||||
label: t("position.intervalsSettings.label"),
|
||||
description: t("position.intervalsSettings.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "number",
|
||||
name: "positionBroadcastSecs",
|
||||
label: "Broadcast Interval",
|
||||
description: "How often your position is sent out over the mesh",
|
||||
label: t("position.broadcastInterval.label"),
|
||||
description: t("position.broadcastInterval.description"),
|
||||
properties: {
|
||||
suffix: "Seconds",
|
||||
suffix: t("unit.second.plural"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "gpsUpdateInterval",
|
||||
label: "GPS Update Interval",
|
||||
description: "How often a GPS fix should be acquired",
|
||||
label: t("position.gpsUpdateInterval.label"),
|
||||
description: t("position.gpsUpdateInterval.description"),
|
||||
properties: {
|
||||
suffix: "Seconds",
|
||||
suffix: t("unit.second.plural"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "broadcastSmartMinimumDistance",
|
||||
label: "Smart Position Minimum Distance",
|
||||
description:
|
||||
"Minimum distance (in meters) that must be traveled before a position update is sent",
|
||||
label: t("position.smartPositionMinDistance.label"),
|
||||
description: t("position.smartPositionMinDistance.description"),
|
||||
disabledBy: [
|
||||
{
|
||||
fieldName: "positionBroadcastSmartEnabled",
|
||||
@@ -141,9 +138,8 @@ export const Position = () => {
|
||||
{
|
||||
type: "number",
|
||||
name: "broadcastSmartMinimumIntervalSecs",
|
||||
label: "Smart Position Minimum Interval",
|
||||
description:
|
||||
"Minimum interval (in seconds) that must pass before a position update is sent",
|
||||
label: t("position.smartPositionMinInterval.label"),
|
||||
description: t("position.smartPositionMinInterval.description"),
|
||||
disabledBy: [
|
||||
{
|
||||
fieldName: "positionBroadcastSmartEnabled",
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import type { PowerValidation } from "@app/validation/config/power.tsx";
|
||||
import type { PowerValidation } from "@app/validation/config/power.ts";
|
||||
import { create } from "@bufbuild/protobuf";
|
||||
import { DynamicForm } from "@components/Form/DynamicForm.tsx";
|
||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||
import { Protobuf } from "@meshtastic/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const Power = () => {
|
||||
const { config, setWorkingConfig } = useDevice();
|
||||
const { t } = useTranslation("deviceConfig");
|
||||
|
||||
const onSubmit = (data: PowerValidation) => {
|
||||
setWorkingConfig(
|
||||
@@ -24,31 +26,29 @@ export const Power = () => {
|
||||
defaultValues={config.power}
|
||||
fieldGroups={[
|
||||
{
|
||||
label: "Power Config",
|
||||
description: "Settings for the power module",
|
||||
label: t("power.powerConfigSettings.label"),
|
||||
description: t("power.powerConfigSettings.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "toggle",
|
||||
name: "isPowerSaving",
|
||||
label: "Enable power saving mode",
|
||||
description:
|
||||
"Select if powered from a low-current source (i.e. solar), to minimize power consumption as much as possible.",
|
||||
label: t("power.powerSavingEnabled.label"),
|
||||
description: t("power.powerSavingEnabled.description"),
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "onBatteryShutdownAfterSecs",
|
||||
label: "Shutdown on battery delay",
|
||||
description:
|
||||
"Automatically shutdown node after this long when on battery, 0 for indefinite",
|
||||
label: t("power.shutdownOnBatteryDelay.label"),
|
||||
description: t("power.shutdownOnBatteryDelay.description"),
|
||||
properties: {
|
||||
suffix: "Seconds",
|
||||
suffix: t("unit.second.plural"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "adcMultiplierOverride",
|
||||
label: "ADC Multiplier Override ratio",
|
||||
description: "Used for tweaking battery voltage reading",
|
||||
label: t("power.adcMultiplierOverride.label"),
|
||||
description: t("power.adcMultiplierOverride.description"),
|
||||
properties: {
|
||||
step: 0.0001,
|
||||
},
|
||||
@@ -56,52 +56,49 @@ export const Power = () => {
|
||||
{
|
||||
type: "number",
|
||||
name: "waitBluetoothSecs",
|
||||
label: "No Connection Bluetooth Disabled",
|
||||
description:
|
||||
"If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long",
|
||||
label: t("power.noConnectionBluetoothDisabled.label"),
|
||||
description: t("power.noConnectionBluetoothDisabled.description"),
|
||||
properties: {
|
||||
suffix: "Seconds",
|
||||
suffix: t("unit.second.plural"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "deviceBatteryInaAddress",
|
||||
label: "INA219 Address",
|
||||
description: "Address of the INA219 battery monitor",
|
||||
label: t("power.ina219Address.label"),
|
||||
description: t("power.ina219Address.description"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Sleep Settings",
|
||||
description: "Sleep settings for the power module",
|
||||
label: t("power.sleepSettings.label"),
|
||||
description: t("power.sleepSettings.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "number",
|
||||
name: "sdsSecs",
|
||||
label: "Super Deep Sleep Duration",
|
||||
description:
|
||||
"How long the device will be in super deep sleep for",
|
||||
label: t("power.superDeepSleepDuration.label"),
|
||||
description: t("power.superDeepSleepDuration.description"),
|
||||
properties: {
|
||||
suffix: "Seconds",
|
||||
suffix: t("unit.second.plural"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "lsSecs",
|
||||
label: "Light Sleep Duration",
|
||||
description: "How long the device will be in light sleep for",
|
||||
label: t("power.lightSleepDuration.label"),
|
||||
description: t("power.lightSleepDuration.description"),
|
||||
properties: {
|
||||
suffix: "Seconds",
|
||||
suffix: t("unit.second.plural"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "minWakeSecs",
|
||||
label: "Minimum Wake Time",
|
||||
description:
|
||||
"Minimum amount of time the device will stay awake for after receiving a packet",
|
||||
label: t("power.minimumWakeTime.label"),
|
||||
description: t("power.minimumWakeTime.description"),
|
||||
properties: {
|
||||
suffix: "Seconds",
|
||||
suffix: t("unit.second.plural"),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -10,6 +10,7 @@ import { fromByteArray, toByteArray } from "base64-js";
|
||||
import { useReducer } from "react";
|
||||
import { securityReducer } from "@components/PageComponents/Config/Security/securityReducer.tsx";
|
||||
import type { SecurityConfigInit } from "./types.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const Security = () => {
|
||||
const { config, setWorkingConfig, setDialogOpen } = useDevice();
|
||||
@@ -21,6 +22,7 @@ export const Security = () => {
|
||||
removeError,
|
||||
clearErrors,
|
||||
} = useAppStore();
|
||||
const { t } = useTranslation("deviceConfig");
|
||||
|
||||
const [state, dispatch] = useReducer(securityReducer, {
|
||||
privateKey: fromByteArray(config.security?.privateKey ?? new Uint8Array(0)),
|
||||
@@ -50,20 +52,18 @@ export const Security = () => {
|
||||
try {
|
||||
removeError(fieldNameKey);
|
||||
if (fieldName === "privateKey" && input === "") {
|
||||
addError(fieldNameKey, "Private Key is required");
|
||||
addError(fieldNameKey, t("security.validation.privateKeyRequired"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (fieldName === "adminKey" && input === "") {
|
||||
if (
|
||||
state.isManaged && state.adminKey
|
||||
.map((v, i) => i === fieldIndex ? input : v)
|
||||
state.isManaged &&
|
||||
state.adminKey
|
||||
.map((v, i) => (i === fieldIndex ? input : v))
|
||||
.every((s) => s === "")
|
||||
) {
|
||||
addError(
|
||||
"adminKey0",
|
||||
"At least one admin key is requred if the node is managed.",
|
||||
);
|
||||
addError("adminKey0", t("security."));
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -72,32 +72,35 @@ export const Security = () => {
|
||||
if (input.length % 4 !== 0) {
|
||||
addError(
|
||||
fieldNameKey,
|
||||
`${
|
||||
fieldName === "privateKey" ? "Private" : "Admin"
|
||||
} Key is required to be a 256 bit pre-shared key (PSK)`,
|
||||
fieldName === "privateKey"
|
||||
? t("security.validation.privateKeyMustBe256BitPsk")
|
||||
: t("security.validation.adminKeyMustBe256BitPsk"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const decoded = toByteArray(input);
|
||||
if (decoded.length !== count) {
|
||||
addError(fieldNameKey, `Please enter a valid ${count * 8} bit PSK`);
|
||||
addError(
|
||||
fieldNameKey,
|
||||
t("security.validation.enterValid256BitPsk", {
|
||||
bits: count * 8,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
addError(
|
||||
fieldNameKey,
|
||||
`Invalid ${
|
||||
fieldName === "privateKey" ? "Private" : "Admin"
|
||||
} Key format`,
|
||||
fieldName === "privateKey"
|
||||
? t("security.validation.invalidPrivateKeyFormat")
|
||||
: t("security.validation.invalidAdminKeyFormat"),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function setSecurityPayload(
|
||||
overrides: SecurityConfigInit,
|
||||
) {
|
||||
function setSecurityPayload(overrides: SecurityConfigInit) {
|
||||
const base: SecurityConfigInit = {
|
||||
isManaged: state.isManaged,
|
||||
adminChannelEnabled: state.adminChannelEnabled,
|
||||
@@ -204,13 +207,12 @@ export const Security = () => {
|
||||
) => {
|
||||
dispatch({ type: "SET_TOGGLE", field, payload: next });
|
||||
|
||||
if (
|
||||
field === "isManaged" && state.adminKey.every((s) => s === "")
|
||||
) {
|
||||
if (field === "isManaged" && state.adminKey.every((s) => s === "")) {
|
||||
if (next) {
|
||||
// If enabling 'managed' and no admin keys are set
|
||||
addError(
|
||||
"adminKey0",
|
||||
"At least one admin key is requred if the node is managed.",
|
||||
t("security.validation.adminKeyRequiredWhenManaged"),
|
||||
);
|
||||
} else {
|
||||
removeError("adminKey0");
|
||||
@@ -252,16 +254,22 @@ export const Security = () => {
|
||||
}}
|
||||
fieldGroups={[
|
||||
{
|
||||
label: "Security Settings",
|
||||
description: "Settings for the Security configuration",
|
||||
label: t("security.title"),
|
||||
description: t("security.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "passwordGenerator",
|
||||
id: "pskInput",
|
||||
name: "privateKey",
|
||||
label: "Private Key",
|
||||
description: "Used to create a shared key with a remote device",
|
||||
bits: [{ text: "256 bit", value: "32", key: "bit256" }],
|
||||
label: t("security.privateKey.label"),
|
||||
description: t("security.privateKey.description"),
|
||||
bits: [
|
||||
{
|
||||
text: t("security.256bit"),
|
||||
value: "32",
|
||||
key: "bit256",
|
||||
},
|
||||
],
|
||||
validationText: hasFieldError("privateKey")
|
||||
? getErrorMessage("privateKey")
|
||||
: "",
|
||||
@@ -271,7 +279,7 @@ export const Security = () => {
|
||||
hide: !state.privateKeyVisible,
|
||||
actionButtons: [
|
||||
{
|
||||
text: "Generate",
|
||||
text: t("button.generate"),
|
||||
onClick: () =>
|
||||
dispatch({
|
||||
type: "SHOW_PRIVATE_KEY_DIALOG",
|
||||
@@ -280,7 +288,7 @@ export const Security = () => {
|
||||
variant: "success",
|
||||
},
|
||||
{
|
||||
text: "Backup Key",
|
||||
text: t("button.backupKey"),
|
||||
onClick: () => setDialogOpen("pkiBackup", true),
|
||||
variant: "subtle",
|
||||
},
|
||||
@@ -294,10 +302,9 @@ export const Security = () => {
|
||||
{
|
||||
type: "text",
|
||||
name: "publicKey",
|
||||
label: "Public Key",
|
||||
label: t("security.publicKey.label"),
|
||||
disabled: true,
|
||||
description:
|
||||
"Sent out to other nodes on the mesh to allow them to compute a shared secret key",
|
||||
description: t("security.publicKey.description"),
|
||||
properties: {
|
||||
value: state.publicKey,
|
||||
showCopyButton: true,
|
||||
@@ -306,22 +313,27 @@ export const Security = () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Admin Settings",
|
||||
description: "Settings for Admin",
|
||||
label: t("security.adminSettings.label"),
|
||||
description: t("security.adminSettings.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "passwordGenerator",
|
||||
name: "adminKey.0",
|
||||
id: "adminKey0Input",
|
||||
label: "Primary Admin Key",
|
||||
description:
|
||||
"The primary public key authorized to send admin messages to this node",
|
||||
label: t("security.primaryAdminKey.label"),
|
||||
description: t("security.primaryAdminKey.description"),
|
||||
validationText: hasFieldError("adminKey0")
|
||||
? getErrorMessage("adminKey0")
|
||||
: "",
|
||||
inputChange: (e) => adminKeyInputChangeEvent(e, 0),
|
||||
selectChange: () => {},
|
||||
bits: [{ text: "256 bit", value: "32", key: "bit256" }],
|
||||
bits: [
|
||||
{
|
||||
text: t("security.256bit"),
|
||||
value: "32",
|
||||
key: "bit256",
|
||||
},
|
||||
],
|
||||
devicePSKBitCount: state.privateKeyBitCount,
|
||||
hide: !state.adminKeyVisible[0],
|
||||
actionButtons: [],
|
||||
@@ -338,15 +350,20 @@ export const Security = () => {
|
||||
type: "passwordGenerator",
|
||||
name: "adminKey.1",
|
||||
id: "adminKey1Input",
|
||||
label: "Secondary Admin Key",
|
||||
description:
|
||||
"The secondary public key authorized to send admin messages to this node",
|
||||
label: t("security.secondaryAdminKey.label"),
|
||||
description: t("security.secondaryAdminKey.description"),
|
||||
validationText: hasFieldError("adminKey1")
|
||||
? getErrorMessage("adminKey1")
|
||||
: "",
|
||||
inputChange: (e) => adminKeyInputChangeEvent(e, 1),
|
||||
selectChange: () => {},
|
||||
bits: [{ text: "256 bit", value: "32", key: "bit256" }],
|
||||
bits: [
|
||||
{
|
||||
text: t("security.256bit"),
|
||||
value: "32",
|
||||
key: "bit256",
|
||||
},
|
||||
],
|
||||
devicePSKBitCount: state.privateKeyBitCount,
|
||||
hide: !state.adminKeyVisible[1],
|
||||
actionButtons: [],
|
||||
@@ -363,15 +380,20 @@ export const Security = () => {
|
||||
type: "passwordGenerator",
|
||||
name: "adminKey.2",
|
||||
id: "adminKey2Input",
|
||||
label: "Tertiary Admin Key",
|
||||
description:
|
||||
"The tertiary public key authorized to send admin messages to this node",
|
||||
label: t("security.tertiaryAdminKey.label"),
|
||||
description: t("security.tertiaryAdminKey.description"),
|
||||
validationText: hasFieldError("adminKey2")
|
||||
? getErrorMessage("adminKey2")
|
||||
: "",
|
||||
inputChange: (e) => adminKeyInputChangeEvent(e, 2),
|
||||
selectChange: () => {},
|
||||
bits: [{ text: "256 bit", value: "32", key: "bit256" }],
|
||||
bits: [
|
||||
{
|
||||
text: t("security.256bit"),
|
||||
value: "32",
|
||||
key: "bit256",
|
||||
},
|
||||
],
|
||||
devicePSKBitCount: state.privateKeyBitCount,
|
||||
hide: !state.adminKeyVisible[2],
|
||||
actionButtons: [],
|
||||
@@ -387,26 +409,22 @@ export const Security = () => {
|
||||
{
|
||||
type: "toggle",
|
||||
name: "isManaged",
|
||||
label: "Managed",
|
||||
description:
|
||||
"If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.",
|
||||
label: t("security.managed.label"),
|
||||
description: t("security.managed.description"),
|
||||
inputChange: (e: boolean) => onToggleChange("isManaged", e),
|
||||
properties: {
|
||||
checked: state.isManaged,
|
||||
},
|
||||
disabled: (
|
||||
(hasFieldError("adminKey0") ||
|
||||
hasFieldError("adminKey1") ||
|
||||
hasFieldError("adminKey2")) &&
|
||||
!state.adminKey.every((s) => s === "")
|
||||
),
|
||||
disabled: (hasFieldError("adminKey0") ||
|
||||
hasFieldError("adminKey1") ||
|
||||
hasFieldError("adminKey2")) &&
|
||||
!state.adminKey.every((s) => s === ""),
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
name: "adminChannelEnabled",
|
||||
label: "Allow Legacy Admin",
|
||||
description:
|
||||
"Allow incoming device control over the insecure legacy admin channel",
|
||||
label: t("security.adminChannelEnabled.label"),
|
||||
description: t("security.adminChannelEnabled.description"),
|
||||
inputChange: (e: boolean) =>
|
||||
onToggleChange("adminChannelEnabled", e),
|
||||
properties: {
|
||||
@@ -416,15 +434,14 @@ export const Security = () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Logging Settings",
|
||||
description: "Settings for Logging",
|
||||
label: t("security.loggingSettings.label"),
|
||||
description: t("security.loggingSettings.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "toggle",
|
||||
name: "debugLogApiEnabled",
|
||||
label: "Enable Debug Log API",
|
||||
description:
|
||||
"Output live debug logging over serial, view and export position-redacted device logs over Bluetooth",
|
||||
label: t("security.enableDebugLogApi.label"),
|
||||
description: t("security.enableDebugLogApi.description"),
|
||||
inputChange: (e: boolean) =>
|
||||
onToggleChange("debugLogApiEnabled", e),
|
||||
properties: {
|
||||
@@ -434,8 +451,8 @@ export const Security = () => {
|
||||
{
|
||||
type: "toggle",
|
||||
name: "serialEnabled",
|
||||
label: "Serial Output Enabled",
|
||||
description: "Serial Console over the Stream API",
|
||||
label: t("security.serialOutputEnabled.label"),
|
||||
description: t("security.serialOutputEnabled.description"),
|
||||
inputChange: (e: boolean) => onToggleChange("serialEnabled", e),
|
||||
properties: {
|
||||
checked: state.serialEnabled,
|
||||
@@ -447,9 +464,9 @@ export const Security = () => {
|
||||
/>
|
||||
<PkiRegenerateDialog
|
||||
text={{
|
||||
button: "Regenerate",
|
||||
title: "Regenerate Key pair?",
|
||||
description: "Are you sure you want to regenerate key pair?",
|
||||
button: t("button.regenerate"),
|
||||
title: t("pkiRegenerate.title"),
|
||||
description: t("pkiRegenerate.description"),
|
||||
}}
|
||||
open={state.privateKeyDialogOpen}
|
||||
onOpenChange={() =>
|
||||
|
||||
Reference in New Issue
Block a user