Browse Source

format

pull/476/head
Sacha Weatherstone 1 year ago
parent
commit
977b5647f6
Failed to extract signature
  1. 19
      src/components/Dialog/ImportDialog.tsx
  2. 6
      src/components/Dialog/LocationResponseDialog.tsx
  3. 273
      src/components/Dialog/NodeDetailsDialog.tsx
  4. 10
      src/components/Dialog/NodeOptionsDialog.tsx
  5. 8
      src/components/Dialog/QRDialog.tsx
  6. 6
      src/components/Dialog/TracerouteResponseDialog.tsx
  7. 65
      src/components/PageComponents/Channel.tsx
  8. 5
      src/components/PageComponents/Config/Bluetooth.tsx
  9. 48
      src/components/PageComponents/Map/NodeDetail.tsx
  10. 8
      src/components/PageComponents/Messages/ChannelChat.tsx
  11. 4
      src/components/PageComponents/Messages/MessageInput.tsx
  12. 36
      src/components/PageComponents/Messages/TraceRoute.tsx
  13. 32
      src/core/stores/deviceStore.ts
  14. 4
      src/pages/Channels.tsx
  15. 65
      src/pages/Messages.tsx
  16. 32
      src/pages/Nodes.tsx
  17. 6
      src/validation/channel.ts
  18. 12
      src/validation/config/bluetooth.ts
  19. 12
      src/validation/config/position.ts
  20. 12
      src/validation/config/security.ts
  21. 12
      src/validation/moduleConfig/ambientLighting.ts
  22. 15
      src/validation/moduleConfig/cannedMessage.ts
  23. 12
      src/validation/moduleConfig/detectionSensor.ts
  24. 12
      src/validation/moduleConfig/externalNotification.ts
  25. 3
      src/validation/moduleConfig/neighborInfo.ts
  26. 3
      src/validation/moduleConfig/paxcounter.ts
  27. 3
      src/validation/moduleConfig/serial.ts
  28. 3
      src/validation/moduleConfig/telemetry.ts

19
src/components/Dialog/ImportDialog.tsx

@ -73,9 +73,10 @@ export const ImportDialog = ({
connection?.setChannel( connection?.setChannel(
create(Protobuf.Channel.ChannelSchema, { create(Protobuf.Channel.ChannelSchema, {
index, index,
role: index === 0 role:
? Protobuf.Channel.Channel_Role.PRIMARY index === 0
: Protobuf.Channel.Channel_Role.SECONDARY, ? Protobuf.Channel.Channel_Role.PRIMARY
: Protobuf.Channel.Channel_Role.SECONDARY,
settings: ch, settings: ch,
}), }),
); );
@ -122,25 +123,21 @@ export const ImportDialog = ({
checked={channelSet?.loraConfig?.usePreset ?? true} checked={channelSet?.loraConfig?.usePreset ?? true}
/> />
</div> </div>
{ {/* <Select
/* <Select
label="Modem Preset" label="Modem Preset"
disabled disabled
value={channelSet?.loraConfig?.modemPreset} value={channelSet?.loraConfig?.modemPreset}
> >
{renderOptions(Protobuf.Config_LoRaConfig_ModemPreset)} {renderOptions(Protobuf.Config_LoRaConfig_ModemPreset)}
</Select> */ </Select> */}
}
</div> </div>
{ {/* <Select
/* <Select
label="Region" label="Region"
disabled disabled
value={channelSet?.loraConfig?.region} value={channelSet?.loraConfig?.region}
> >
{renderOptions(Protobuf.Config_LoRaConfig_RegionCode)} {renderOptions(Protobuf.Config_LoRaConfig_RegionCode)}
</Select> */ </Select> */}
}
<span className="text-md block font-medium text-text-primary"> <span className="text-md block font-medium text-text-primary">
Channels: Channels:

6
src/components/Dialog/LocationResponseDialog.tsx

@ -24,9 +24,11 @@ export const LocationResponseDialog = ({
const { nodes } = useDevice(); const { nodes } = useDevice();
const from = nodes.get(location?.from ?? 0); const from = nodes.get(location?.from ?? 0);
const longName = from?.user?.longName ?? const longName =
from?.user?.longName ??
(from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown"); (from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown");
const shortName = from?.user?.shortName ?? const shortName =
from?.user?.shortName ??
(from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK"); (from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK");
return ( return (

273
src/components/Dialog/NodeDetailsDialog.tsx

@ -32,159 +32,134 @@ export const NodeDetailsDialog = ({
const { nodeNumDetails } = useAppStore(); const { nodeNumDetails } = useAppStore();
const device: Protobuf.Mesh.NodeInfo = nodes.get(nodeNumDetails); const device: Protobuf.Mesh.NodeInfo = nodes.get(nodeNumDetails);
return device return device ? (
? ( <Dialog open={open} onOpenChange={onOpenChange}>
<Dialog open={open} onOpenChange={onOpenChange}> <DialogContent>
<DialogContent> <DialogHeader>
<DialogHeader> <DialogTitle>
<DialogTitle> Node Details for {device.user?.longName ?? "UNKNOWN"} (
Node Details for {device.user?.longName ?? "UNKNOWN"} ( {device.user?.shortName ?? "UNK"})
{device.user?.shortName ?? "UNK"}) </DialogTitle>
</DialogTitle> </DialogHeader>
</DialogHeader> <DialogFooter>
<DialogFooter> <div className="w-full">
<div className="w-full"> <DeviceImage
<DeviceImage className="w-32 h-32 mx-auto rounded-lg border-4 border-slate-200 dark:border-slate-800"
className="w-32 h-32 mx-auto rounded-lg border-4 border-slate-200 dark:border-slate-800" deviceType={
deviceType={Protobuf.Mesh Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]
.HardwareModel[device.user?.hwModel ?? 0]} }
/> />
<div className="bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3"> <div className="bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50"> <p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Details: Details:
</p> </p>
<p> <p>
Hardware:{" "} Hardware:{" "}
{Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]} {Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]}
</p> </p>
<p>Node Number: {device.num}</p> <p>Node Number: {device.num}</p>
<p>Node HEX: !{numberToHexUnpadded(device.num)}</p> <p>Node HEX: !{numberToHexUnpadded(device.num)}</p>
<p> <p>
Role: {Protobuf.Config.Config_DeviceConfig_Role[ Role:{" "}
{
Protobuf.Config.Config_DeviceConfig_Role[
device.user?.role ?? 0 device.user?.role ?? 0
]} ]
</p> }
<p> </p>
Last Heard: {device.lastHeard === 0 <p>
? ( Last Heard:{" "}
"Never" {device.lastHeard === 0 ? (
) "Never"
: <TimeAgo timestamp={device.lastHeard * 1000} />} ) : (
<TimeAgo timestamp={device.lastHeard * 1000} />
)}
</p>
</div>
{device.position ? (
<div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Position:
</p> </p>
{device.position.latitudeI && device.position.longitudeI ? (
<p>
Coordinates:{" "}
<a
className="text-blue-500 dark:text-blue-400"
href={`https://www.openstreetmap.org/?mlat=${
device.position.latitudeI / 1e7
}&mlon=${device.position.longitudeI / 1e7}&layers=N`}
target="_blank"
rel="noreferrer"
>
{device.position.latitudeI / 1e7},{" "}
{device.position.longitudeI / 1e7}
</a>
</p>
) : null}
{device.position.altitude ? (
<p>Altitude: {device.position.altitude}m</p>
) : null}
</div> </div>
) : null}
{device.position {device.deviceMetrics ? (
? ( <div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3"> <p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50"> Device Metrics:
Position: </p>
</p> {device.deviceMetrics.airUtilTx ? (
{device.position.latitudeI && device.position.longitudeI <p>
? ( Air TX utilization:{" "}
<p> {device.deviceMetrics.airUtilTx.toFixed(2)}%
Coordinates:{" "} </p>
<a ) : null}
className="text-blue-500 dark:text-blue-400" {device.deviceMetrics.channelUtilization ? (
href={`https://www.openstreetmap.org/?mlat=${ <p>
device.position.latitudeI / 1e7 Channel utilization:{" "}
}&mlon=${ {device.deviceMetrics.channelUtilization.toFixed(2)}%
device.position.longitudeI / 1e7 </p>
}&layers=N`} ) : null}
target="_blank" {device.deviceMetrics.batteryLevel ? (
rel="noreferrer" <p>
> Battery level:{" "}
{device.position.latitudeI / 1e7},{" "} {device.deviceMetrics.batteryLevel.toFixed(2)}%
{device.position.longitudeI / 1e7} </p>
</a> ) : null}
</p> {device.deviceMetrics.voltage ? (
) <p>Voltage: {device.deviceMetrics.voltage.toFixed(2)}V</p>
: null} ) : null}
{device.position.altitude {device.deviceMetrics.uptimeSeconds ? (
? <p>Altitude: {device.position.altitude}m</p> <p>
: null} Uptime:{" "}
</div> <Uptime seconds={device.deviceMetrics.uptimeSeconds} />
) </p>
: null} ) : null}
</div>
{device.deviceMetrics ) : null}
? (
<div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Device Metrics:
</p>
{device.deviceMetrics.airUtilTx
? (
<p>
Air TX utilization:{" "}
{device.deviceMetrics.airUtilTx.toFixed(2)}%
</p>
)
: null}
{device.deviceMetrics.channelUtilization
? (
<p>
Channel utilization:{" "}
{device.deviceMetrics.channelUtilization.toFixed(2)}%
</p>
)
: null}
{device.deviceMetrics.batteryLevel
? (
<p>
Battery level:{" "}
{device.deviceMetrics.batteryLevel.toFixed(2)}%
</p>
)
: null}
{device.deviceMetrics.voltage
? (
<p>
Voltage: {device.deviceMetrics.voltage.toFixed(2)}V
</p>
)
: null}
{device.deviceMetrics.uptimeSeconds
? (
<p>
Uptime:{" "}
<Uptime
seconds={device.deviceMetrics.uptimeSeconds}
/>
</p>
)
: null}
</div>
)
: null}
{device {device ? (
? ( <div className="mt-5 w-full max-w-[464px] bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<div className="mt-5 w-full max-w-[464px] bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3"> <Accordion className="AccordionRoot" type="single" collapsible>
<Accordion <AccordionItem className="AccordionItem" value="item-1">
className="AccordionRoot" <AccordionTrigger>
type="single" <p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
collapsible All Raw Metrics:
> </p>
<AccordionItem className="AccordionItem" value="item-1"> </AccordionTrigger>
<AccordionTrigger> <AccordionContent className="overflow-x-scroll">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50"> <pre className="text-xs w-full">
All Raw Metrics:
</p>
</AccordionTrigger>
<AccordionContent className="overflow-x-scroll">
<pre className="text-xs w-full">
{JSON.stringify(device, null, 2)} {JSON.stringify(device, null, 2)}
</pre> </pre>
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
</div> </div>
) ) : null}
: null} </div>
</div> </DialogFooter>
</DialogFooter> </DialogContent>
</DialogContent> </Dialog>
</Dialog> ) : null;
)
: null;
}; };

10
src/components/Dialog/NodeOptionsDialog.tsx

@ -31,9 +31,11 @@ export const NodeOptionsDialog = ({
setChatType, setChatType,
setActiveChat, setActiveChat,
} = useAppStore(); } = useAppStore();
const longName = node?.user?.longName ?? const longName =
node?.user?.longName ??
(node ? `!${numberToHexUnpadded(node?.num)}` : "Unknown"); (node ? `!${numberToHexUnpadded(node?.num)}` : "Unknown");
const shortName = node?.user?.shortName ?? const shortName =
node?.user?.shortName ??
(node ? `${numberToHexUnpadded(node?.num).substring(0, 4)}` : "UNK"); (node ? `${numberToHexUnpadded(node?.num).substring(0, 4)}` : "UNK");
function handleDirectMessage() { function handleDirectMessage() {
@ -51,7 +53,7 @@ export const NodeOptionsDialog = ({
connection?.requestPosition(node.num).then(() => connection?.requestPosition(node.num).then(() =>
toast({ toast({
title: "Position request sent.", title: "Position request sent.",
}) }),
); );
onOpenChange(); onOpenChange();
} }
@ -64,7 +66,7 @@ export const NodeOptionsDialog = ({
connection?.traceRoute(node.num).then(() => connection?.traceRoute(node.num).then(() =>
toast({ toast({
title: "Traceroute sent.", title: "Traceroute sent.",
}) }),
); );
onOpenChange(); onOpenChange();
} }

8
src/components/Dialog/QRDialog.tsx

@ -77,8 +77,8 @@ export const QRDialog = ({
{channel.settings?.name.length {channel.settings?.name.length
? channel.settings.name ? channel.settings.name
: channel.role === Protobuf.Channel.Channel_Role.PRIMARY : channel.role === Protobuf.Channel.Channel_Role.PRIMARY
? "Primary" ? "Primary"
: `Channel: ${channel.index}`} : `Channel: ${channel.index}`}
</Label> </Label>
<Checkbox <Checkbox
key={channel.index} key={channel.index}
@ -86,9 +86,7 @@ export const QRDialog = ({
onCheckedChange={() => { onCheckedChange={() => {
if (selectedChannels.includes(channel.index)) { if (selectedChannels.includes(channel.index)) {
setSelectedChannels( setSelectedChannels(
selectedChannels.filter((c) => selectedChannels.filter((c) => c !== channel.index),
c !== channel.index
),
); );
} else { } else {
setSelectedChannels([ setSelectedChannels([

6
src/components/Dialog/TracerouteResponseDialog.tsx

@ -28,9 +28,11 @@ export const TracerouteResponseDialog = ({
const snrTowards = traceroute?.data.snrTowards ?? []; const snrTowards = traceroute?.data.snrTowards ?? [];
const snrBack = traceroute?.data.snrBack ?? []; const snrBack = traceroute?.data.snrBack ?? [];
const from = nodes.get(traceroute?.from ?? 0); const from = nodes.get(traceroute?.from ?? 0);
const longName = from?.user?.longName ?? const longName =
from?.user?.longName ??
(from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown"); (from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown");
const shortName = from?.user?.shortName ?? const shortName =
from?.user?.shortName ??
(from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK"); (from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK");
const to = nodes.get(traceroute?.to ?? 0); const to = nodes.get(traceroute?.to ?? 0);
return ( return (

65
src/components/PageComponents/Channel.tsx

@ -24,9 +24,8 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
channel?.settings?.psk.length ?? 16, channel?.settings?.psk.length ?? 16,
); );
const [validationText, setValidationText] = useState<string>(); const [validationText, setValidationText] = useState<string>();
const [preSharedDialogOpen, setPreSharedDialogOpen] = useState<boolean>( const [preSharedDialogOpen, setPreSharedDialogOpen] =
false, useState<boolean>(false);
);
const onSubmit = (data: ChannelValidation) => { const onSubmit = (data: ChannelValidation) => {
const channel = create(Protobuf.Channel.ChannelSchema, { const channel = create(Protobuf.Channel.ChannelSchema, {
@ -108,7 +107,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
channel?.settings?.moduleSettings?.positionPrecision === 32, channel?.settings?.moduleSettings?.positionPrecision === 32,
positionPrecision: positionPrecision:
channel?.settings?.moduleSettings?.positionPrecision === channel?.settings?.moduleSettings?.positionPrecision ===
undefined undefined
? 10 ? 10
: channel?.settings?.moduleSettings?.positionPrecision, : channel?.settings?.moduleSettings?.positionPrecision,
}, },
@ -127,9 +126,10 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
description: description:
"Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed",
properties: { properties: {
enumValue: channel.index === 0 enumValue:
? { PRIMARY: 1 } channel.index === 0
: { DISABLED: 0, SECONDARY: 2 }, ? { PRIMARY: 1 }
: { DISABLED: 0, SECONDARY: 2 },
}, },
}, },
{ {
@ -192,31 +192,32 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
description: description:
"If not sharing precise location, position shared on channel will be accurate within this distance", "If not sharing precise location, position shared on channel will be accurate within this distance",
properties: { properties: {
enumValue: config.display?.units === 0 enumValue:
? { config.display?.units === 0
"Within 23 km": 10, ? {
"Within 12 km": 11, "Within 23 km": 10,
"Within 5.8 km": 12, "Within 12 km": 11,
"Within 2.9 km": 13, "Within 5.8 km": 12,
"Within 1.5 km": 14, "Within 2.9 km": 13,
"Within 700 m": 15, "Within 1.5 km": 14,
"Within 350 m": 16, "Within 700 m": 15,
"Within 200 m": 17, "Within 350 m": 16,
"Within 90 m": 18, "Within 200 m": 17,
"Within 50 m": 19, "Within 90 m": 18,
} "Within 50 m": 19,
: { }
"Within 15 miles": 10, : {
"Within 7.3 miles": 11, "Within 15 miles": 10,
"Within 3.6 miles": 12, "Within 7.3 miles": 11,
"Within 1.8 miles": 13, "Within 3.6 miles": 12,
"Within 0.9 miles": 14, "Within 1.8 miles": 13,
"Within 0.5 miles": 15, "Within 0.9 miles": 14,
"Within 0.2 miles": 16, "Within 0.5 miles": 15,
"Within 600 feet": 17, "Within 0.2 miles": 16,
"Within 300 feet": 18, "Within 600 feet": 17,
"Within 150 feet": 19, "Within 300 feet": 18,
}, "Within 150 feet": 19,
},
}, },
}, },
], ],

5
src/components/PageComponents/Config/Bluetooth.tsx

@ -111,8 +111,9 @@ export const Bluetooth = () => {
disabledBy: [ disabledBy: [
{ {
fieldName: "mode", fieldName: "mode",
selector: Protobuf.Config.Config_BluetoothConfig_PairingMode selector:
.FIXED_PIN, Protobuf.Config.Config_BluetoothConfig_PairingMode
.FIXED_PIN,
invert: true, invert: true,
}, },
{ {

48
src/components/PageComponents/Map/NodeDetail.tsx

@ -37,23 +37,21 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
<Avatar text={node.user?.shortName} /> <Avatar text={node.user?.shortName} />
<div> <div>
{node.user?.publicKey && node.user?.publicKey.length > 0 {node.user?.publicKey && node.user?.publicKey.length > 0 ? (
? ( <LockIcon
<LockIcon className="text-green-600"
className="text-green-600" size={12}
size={12} strokeWidth={3}
strokeWidth={3} aria-label="Public Key Enabled"
aria-label="Public Key Enabled" />
/> ) : (
) <LockOpenIcon
: ( className="text-yellow-500"
<LockOpenIcon size={12}
className="text-yellow-500" strokeWidth={3}
size={12} aria-label="No Public Key"
strokeWidth={3} />
aria-label="No Public Key" )}
/>
)}
</div> </div>
<Star <Star
@ -75,13 +73,15 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
node.deviceMetrics?.voltage?.toPrecision(3) ?? "Unknown" node.deviceMetrics?.voltage?.toPrecision(3) ?? "Unknown"
} volts`} } volts`}
> >
{node.deviceMetrics?.batteryLevel > 100 {node.deviceMetrics?.batteryLevel > 100 ? (
? <BatteryChargingIcon size={22} /> <BatteryChargingIcon size={22} />
: node.deviceMetrics?.batteryLevel > 80 ) : node.deviceMetrics?.batteryLevel > 80 ? (
? <BatteryFullIcon size={22} /> <BatteryFullIcon size={22} />
: node.deviceMetrics?.batteryLevel > 20 ) : node.deviceMetrics?.batteryLevel > 20 ? (
? <BatteryMediumIcon size={22} /> <BatteryMediumIcon size={22} />
: <BatteryLowIcon size={22} />} ) : (
<BatteryLowIcon size={22} />
)}
<Subtle aria-label="Battery"> <Subtle aria-label="Battery">
{node.deviceMetrics?.batteryLevel > 100 {node.deviceMetrics?.batteryLevel > 100
? "Charging" ? "Charging"

8
src/components/PageComponents/Messages/ChannelChat.tsx

@ -34,7 +34,8 @@ export const ChannelChat = ({
const scrollToBottom = useCallback(() => { const scrollToBottom = useCallback(() => {
const scrollContainer = scrollContainerRef.current; const scrollContainer = scrollContainerRef.current;
if (scrollContainer) { if (scrollContainer) {
const isNearBottom = scrollContainer.scrollHeight - const isNearBottom =
scrollContainer.scrollHeight -
scrollContainer.scrollTop - scrollContainer.scrollTop -
scrollContainer.clientHeight < scrollContainer.clientHeight <
100; 100;
@ -71,8 +72,9 @@ export const ChannelChat = ({
key={message.id} key={message.id}
message={message} message={message}
sender={nodes.get(message.from)} sender={nodes.get(message.from)}
lastMsgSameUser={index > 0 && lastMsgSameUser={
messages[index - 1].from === message.from} index > 0 && messages[index - 1].from === message.from
}
/> />
); );
})} })}

4
src/components/PageComponents/Messages/MessageInput.tsx

@ -51,7 +51,7 @@ export const MessageInput = ({
myNodeNum, myNodeNum,
id, id,
"ack", "ack",
) ),
) )
.catch((e: Types.PacketError) => .catch((e: Types.PacketError) =>
setMessageState( setMessageState(
@ -61,7 +61,7 @@ export const MessageInput = ({
myNodeNum, myNodeNum,
e.id, e.id,
e.error, e.error,
) ),
); );
}, },
[channel, connection, myNodeNum, setMessageState, to], [channel, connection, myNodeNum, setMessageState, to],

36
src/components/PageComponents/Messages/TraceRoute.tsx

@ -38,25 +38,23 @@ export const TraceRoute = ({
))} ))}
{from?.user?.longName} {from?.user?.longName}
</span> </span>
{routeBack {routeBack ? (
? ( <span className="ml-4 border-l-2 border-l-background-primary pl-2 text-text-primary">
<span className="ml-4 border-l-2 border-l-background-primary pl-2 text-text-primary"> <p className="font-semibold">Route back:</p>
<p className="font-semibold">Route back:</p> <p>{from?.user?.longName}</p>
<p>{from?.user?.longName}</p> <p> {snrBack?.[0] ? snrBack[0] : "??"}dB</p>
<p> {snrBack?.[0] ? snrBack[0] : "??"}dB</p> {routeBack.map((hop, i) => (
{routeBack.map((hop, i) => ( <span key={nodes.get(hop)?.num}>
<span key={nodes.get(hop)?.num}> <p>
<p> {nodes.get(hop)?.user?.longName ??
{nodes.get(hop)?.user?.longName ?? `!${numberToHexUnpadded(hop)}`}
`!${numberToHexUnpadded(hop)}`} </p>
</p> <p> {snrBack?.[i + 1] ? snrBack[i + 1] : "??"}dB</p>
<p> {snrBack?.[i + 1] ? snrBack[i + 1] : "??"}dB</p> </span>
</span> ))}
))} {to?.user?.longName}
{to?.user?.longName} </span>
</span> ) : null}
)
: null}
</div> </div>
); );
}; };

32
src/core/stores/deviceStore.ts

@ -299,11 +299,11 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
if (!device) { if (!device) {
return; return;
} }
const workingModuleConfigIndex = device?.workingModuleConfig const workingModuleConfigIndex =
.findIndex( device?.workingModuleConfig.findIndex(
(wmc) => (wmc) =>
wmc.payloadVariant.case === wmc.payloadVariant.case ===
moduleConfig.payloadVariant.case, moduleConfig.payloadVariant.case,
); );
if (workingModuleConfigIndex !== -1) { if (workingModuleConfigIndex !== -1) {
device.workingModuleConfig[workingModuleConfigIndex] = device.workingModuleConfig[workingModuleConfigIndex] =
@ -445,7 +445,8 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
if (!device) { if (!device) {
return; return;
} }
const currentNode = device.nodes.get(user.from) ?? const currentNode =
device.nodes.get(user.from) ??
create(Protobuf.Mesh.NodeInfoSchema); create(Protobuf.Mesh.NodeInfoSchema);
currentNode.user = user.data; currentNode.user = user.data;
device.nodes.set(user.from, currentNode); device.nodes.set(user.from, currentNode);
@ -459,7 +460,8 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
if (!device) { if (!device) {
return; return;
} }
const currentNode = device.nodes.get(position.from) ?? const currentNode =
device.nodes.get(position.from) ??
create(Protobuf.Mesh.NodeInfoSchema); create(Protobuf.Mesh.NodeInfoSchema);
currentNode.position = position.data; currentNode.position = position.data;
device.nodes.set(position.from, currentNode); device.nodes.set(position.from, currentNode);
@ -484,11 +486,12 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
return; return;
} }
const messageGroup = device.messages[message.type]; const messageGroup = device.messages[message.type];
const messageIndex = message.type === "direct" const messageIndex =
? message.from === device.hardware.myNodeNum message.type === "direct"
? message.to ? message.from === device.hardware.myNodeNum
: message.from ? message.to
: message.channel; : message.from
: message.channel;
const messages = messageGroup.get(messageIndex); const messages = messageGroup.get(messageIndex);
if (messages) { if (messages) {
@ -561,9 +564,12 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
} }
const messageGroup = device.messages[type]; const messageGroup = device.messages[type];
const messageIndex = type === "direct" const messageIndex =
? from === device.hardware.myNodeNum ? to : from type === "direct"
: channelIndex; ? from === device.hardware.myNodeNum
? to
: from
: channelIndex;
const messages = messageGroup.get(messageIndex); const messages = messageGroup.get(messageIndex);
if (!messages) { if (!messages) {

4
src/pages/Channels.tsx

@ -17,8 +17,8 @@ export const getChannelName = (channel: Protobuf.Channel.Channel) =>
channel.settings?.name.length channel.settings?.name.length
? channel.settings?.name ? channel.settings?.name
: channel.index === 0 : channel.index === 0
? "Primary" ? "Primary"
: `Ch ${channel.index}`; : `Ch ${channel.index}`;
const ChannelsPage = () => { const ChannelsPage = () => {
const { channels, setDialogOpen } = useDevice(); const { channels, setDialogOpen } = useDevice();

65
src/pages/Messages.tsx

@ -39,11 +39,13 @@ export const MessagesPage = () => {
{filteredChannels.map((channel) => ( {filteredChannels.map((channel) => (
<SidebarButton <SidebarButton
key={channel.index} key={channel.index}
label={channel.settings?.name.length label={
? channel.settings?.name channel.settings?.name.length
: channel.index === 0 ? channel.settings?.name
? "Primary" : channel.index === 0
: `Ch ${channel.index}`} ? "Primary"
: `Ch ${channel.index}`
}
active={activeChat === channel.index} active={activeChat === channel.index}
onClick={() => { onClick={() => {
setChatType("broadcast"); setChatType("broadcast");
@ -67,8 +69,9 @@ export const MessagesPage = () => {
{filteredNodes.map((node) => ( {filteredNodes.map((node) => (
<SidebarButton <SidebarButton
key={node.num} key={node.num}
label={node.user?.longName ?? label={
`!${numberToHexUnpadded(node.num)}`} node.user?.longName ?? `!${numberToHexUnpadded(node.num)}`
}
active={activeChat === node.num} active={activeChat === node.num}
onClick={() => { onClick={() => {
setChatType("direct"); setChatType("direct");
@ -91,30 +94,32 @@ export const MessagesPage = () => {
chatType === "broadcast" && currentChannel chatType === "broadcast" && currentChannel
? getChannelName(currentChannel) ? getChannelName(currentChannel)
: chatType === "direct" && nodes.get(activeChat) : chatType === "direct" && nodes.get(activeChat)
? (nodes.get(activeChat)?.user?.longName ?? nodeHex) ? (nodes.get(activeChat)?.user?.longName ?? nodeHex)
: "Loading..." : "Loading..."
}`} }`}
actions={chatType === "direct" actions={
? [ chatType === "direct"
{ ? [
icon: nodes.get(activeChat)?.user?.publicKey.length {
? LockIcon icon: nodes.get(activeChat)?.user?.publicKey.length
: LockOpenIcon, ? LockIcon
iconClasses: nodes.get(activeChat)?.user?.publicKey.length : LockOpenIcon,
? "text-green-600" iconClasses: nodes.get(activeChat)?.user?.publicKey.length
: "text-yellow-300", ? "text-green-600"
async onClick() { : "text-yellow-300",
const targetNode = nodes.get(activeChat)?.num; async onClick() {
if (targetNode === undefined) return; const targetNode = nodes.get(activeChat)?.num;
toast({ if (targetNode === undefined) return;
title: nodes.get(activeChat)?.user?.publicKey.length toast({
? "Chat is using PKI encryption." title: nodes.get(activeChat)?.user?.publicKey.length
: "Chat is using PSK encryption.", ? "Chat is using PKI encryption."
}); : "Chat is using PSK encryption.",
}, });
}, },
] },
: []} ]
: []
}
> >
{allChannels.map( {allChannels.map(
(channel) => (channel) =>

32
src/pages/Nodes.tsx

@ -107,11 +107,9 @@ const NodesPage = (): JSX.Element => {
> >
{node.user?.shortName ?? {node.user?.shortName ??
(node.user?.macaddr (node.user?.macaddr
? `${ ? `${base16
base16
.stringify(node.user?.macaddr.subarray(4, 6) ?? []) .stringify(node.user?.macaddr.subarray(4, 6) ?? [])
.toLowerCase() .toLowerCase()}`
}`
: `${numberToHexUnpadded(node.num).slice(-4)}`)} : `${numberToHexUnpadded(node.num).slice(-4)}`)}
</h1>, </h1>,
@ -122,11 +120,9 @@ const NodesPage = (): JSX.Element => {
> >
{node.user?.longName ?? {node.user?.longName ??
(node.user?.macaddr (node.user?.macaddr
? `Meshtastic ${ ? `Meshtastic ${base16
base16
.stringify(node.user?.macaddr.subarray(4, 6) ?? []) .stringify(node.user?.macaddr.subarray(4, 6) ?? [])
.toLowerCase() .toLowerCase()}`
}`
: `!${numberToHexUnpadded(node.num)}`)} : `!${numberToHexUnpadded(node.num)}`)}
</h1>, </h1>,
@ -140,9 +136,11 @@ const NodesPage = (): JSX.Element => {
?.join(":") ?? "UNK"} ?.join(":") ?? "UNK"}
</Mono>, </Mono>,
<Fragment key="lastHeard"> <Fragment key="lastHeard">
{node.lastHeard === 0 {node.lastHeard === 0 ? (
? <p>Never</p> <p>Never</p>
: <TimeAgo timestamp={node.lastHeard * 1000} />} ) : (
<TimeAgo timestamp={node.lastHeard * 1000} />
)}
</Fragment>, </Fragment>,
<Mono key="snr"> <Mono key="snr">
{node.snr}db/ {node.snr}db/
@ -150,17 +148,19 @@ const NodesPage = (): JSX.Element => {
{(node.snr + 10) * 5}raw {(node.snr + 10) * 5}raw
</Mono>, </Mono>,
<Mono key="pki"> <Mono key="pki">
{node.user?.publicKey && node.user?.publicKey.length > 0 {node.user?.publicKey && node.user?.publicKey.length > 0 ? (
? <LockIcon className="text-green-600" /> <LockIcon className="text-green-600" />
: <LockOpenIcon className="text-yellow-300 mx-auto" />} ) : (
<LockOpenIcon className="text-yellow-300 mx-auto" />
)}
</Mono>, </Mono>,
<Mono key="hops"> <Mono key="hops">
{node.lastHeard !== 0 {node.lastHeard !== 0
? node.viaMqtt === false && node.hopsAway === 0 ? node.viaMqtt === false && node.hopsAway === 0
? "Direct" ? "Direct"
: `${node.hopsAway.toString()} ${ : `${node.hopsAway.toString()} ${
node.hopsAway > 1 ? "hops" : "hop" node.hopsAway > 1 ? "hops" : "hop"
} away` } away`
: "-"} : "-"}
{node.viaMqtt === true ? ", via MQTT" : ""} {node.viaMqtt === true ? ", via MQTT" : ""}
</Mono>, </Mono>,

6
src/validation/channel.ts

@ -10,7 +10,8 @@ import {
} from "class-validator"; } from "class-validator";
export class ChannelValidation export class ChannelValidation
implements Omit<Protobuf.Channel.Channel, keyof Message | "settings"> { implements Omit<Protobuf.Channel.Channel, keyof Message | "settings">
{
@IsNumber() @IsNumber()
index: number; index: number;
@ -21,7 +22,8 @@ export class ChannelValidation
} }
export class Channel_SettingsValidation export class Channel_SettingsValidation
implements Omit<Protobuf.Channel.ChannelSettings, keyof Message | "psk"> { implements Omit<Protobuf.Channel.ChannelSettings, keyof Message | "psk">
{
@IsNumber() @IsNumber()
channelNum: number; channelNum: number;

12
src/validation/config/bluetooth.ts

@ -2,11 +2,13 @@ import type { Message } from "@bufbuild/protobuf";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsEnum, IsInt } from "class-validator"; import { IsBoolean, IsEnum, IsInt } from "class-validator";
export class BluetoothValidation implements export class BluetoothValidation
Omit< implements
Protobuf.Config.Config_BluetoothConfig, Omit<
keyof Message | "deviceLoggingEnabled" Protobuf.Config.Config_BluetoothConfig,
> { keyof Message | "deviceLoggingEnabled"
>
{
@IsBoolean() @IsBoolean()
enabled: boolean; enabled: boolean;

12
src/validation/config/position.ts

@ -4,11 +4,13 @@ import { IsArray, IsBoolean, IsEnum, IsInt } from "class-validator";
const DeprecatedPositionValidationFields = ["gpsEnabled", "gpsAttemptTime"]; const DeprecatedPositionValidationFields = ["gpsEnabled", "gpsAttemptTime"];
export class PositionValidation implements export class PositionValidation
Omit< implements
Protobuf.Config.Config_PositionConfig, Omit<
keyof Message | (typeof DeprecatedPositionValidationFields)[number] Protobuf.Config.Config_PositionConfig,
> { keyof Message | (typeof DeprecatedPositionValidationFields)[number]
>
{
@IsInt() @IsInt()
positionBroadcastSecs: number; positionBroadcastSecs: number;

12
src/validation/config/security.ts

@ -2,11 +2,13 @@ import type { Message } from "@bufbuild/protobuf";
import type { Protobuf } from "@meshtastic/core"; import type { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsString } from "class-validator"; import { IsBoolean, IsString } from "class-validator";
export class SecurityValidation implements export class SecurityValidation
Omit< implements
Protobuf.Config.Config_SecurityConfig, Omit<
keyof Message | "adminKey" | "privateKey" | "publicKey" Protobuf.Config.Config_SecurityConfig,
> { keyof Message | "adminKey" | "privateKey" | "publicKey"
>
{
@IsBoolean() @IsBoolean()
adminChannelEnabled: boolean; adminChannelEnabled: boolean;

12
src/validation/moduleConfig/ambientLighting.ts

@ -2,11 +2,13 @@ import type { Message } from "@bufbuild/protobuf";
import type { Protobuf } from "@meshtastic/core"; import type { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsInt } from "class-validator"; import { IsBoolean, IsInt } from "class-validator";
export class AmbientLightingValidation implements export class AmbientLightingValidation
Omit< implements
Protobuf.ModuleConfig.ModuleConfig_AmbientLightingConfig, Omit<
keyof Message Protobuf.ModuleConfig.ModuleConfig_AmbientLightingConfig,
> { keyof Message
>
{
@IsBoolean() @IsBoolean()
ledState: boolean; ledState: boolean;

15
src/validation/moduleConfig/cannedMessage.ts

@ -4,10 +4,8 @@ import { IsBoolean, IsEnum, IsInt, Length } from "class-validator";
export class CannedMessageValidation export class CannedMessageValidation
implements implements
Omit< Omit<Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig, keyof Message>
Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig, {
keyof Message
> {
@IsBoolean() @IsBoolean()
rotary1Enabled: boolean; rotary1Enabled: boolean;
@ -21,16 +19,13 @@ export class CannedMessageValidation
inputbrokerPinPress: number; inputbrokerPinPress: number;
@IsEnum(Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar) @IsEnum(Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar)
inputbrokerEventCw: inputbrokerEventCw: Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar;
Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar;
@IsEnum(Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar) @IsEnum(Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar)
inputbrokerEventCcw: inputbrokerEventCcw: Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar;
Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar;
@IsEnum(Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar) @IsEnum(Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar)
inputbrokerEventPress: inputbrokerEventPress: Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar;
Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar;
@IsBoolean() @IsBoolean()
updown1Enabled: boolean; updown1Enabled: boolean;

12
src/validation/moduleConfig/detectionSensor.ts

@ -2,11 +2,13 @@ import type { Message } from "@bufbuild/protobuf";
import type { Protobuf } from "@meshtastic/core"; import type { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsInt, Length } from "class-validator"; import { IsBoolean, IsInt, Length } from "class-validator";
export class DetectionSensorValidation implements export class DetectionSensorValidation
Omit< implements
Protobuf.ModuleConfig.ModuleConfig_DetectionSensorConfig, Omit<
keyof Message Protobuf.ModuleConfig.ModuleConfig_DetectionSensorConfig,
> { keyof Message
>
{
@IsBoolean() @IsBoolean()
enabled: boolean; enabled: boolean;

12
src/validation/moduleConfig/externalNotification.ts

@ -2,11 +2,13 @@ import type { Message } from "@bufbuild/protobuf";
import type { Protobuf } from "@meshtastic/core"; import type { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsInt } from "class-validator"; import { IsBoolean, IsInt } from "class-validator";
export class ExternalNotificationValidation implements export class ExternalNotificationValidation
Omit< implements
Protobuf.ModuleConfig.ModuleConfig_ExternalNotificationConfig, Omit<
keyof Message Protobuf.ModuleConfig.ModuleConfig_ExternalNotificationConfig,
> { keyof Message
>
{
@IsBoolean() @IsBoolean()
enabled: boolean; enabled: boolean;

3
src/validation/moduleConfig/neighborInfo.ts

@ -4,7 +4,8 @@ import { IsBoolean, IsInt } from "class-validator";
export class NeighborInfoValidation export class NeighborInfoValidation
implements implements
Omit<Protobuf.ModuleConfig.ModuleConfig_NeighborInfoConfig, keyof Message> { Omit<Protobuf.ModuleConfig.ModuleConfig_NeighborInfoConfig, keyof Message>
{
@IsBoolean() @IsBoolean()
enabled: boolean; enabled: boolean;

3
src/validation/moduleConfig/paxcounter.ts

@ -4,7 +4,8 @@ import { IsBoolean, IsInt } from "class-validator";
export class PaxcounterValidation export class PaxcounterValidation
implements implements
Omit<Protobuf.ModuleConfig.ModuleConfig_PaxcounterConfig, keyof Message> { Omit<Protobuf.ModuleConfig.ModuleConfig_PaxcounterConfig, keyof Message>
{
@IsBoolean() @IsBoolean()
enabled: boolean; enabled: boolean;

3
src/validation/moduleConfig/serial.ts

@ -4,7 +4,8 @@ import { IsBoolean, IsEnum, IsInt } from "class-validator";
export class SerialValidation export class SerialValidation
implements implements
Omit<Protobuf.ModuleConfig.ModuleConfig_SerialConfig, keyof Message> { Omit<Protobuf.ModuleConfig.ModuleConfig_SerialConfig, keyof Message>
{
@IsBoolean() @IsBoolean()
enabled: boolean; enabled: boolean;

3
src/validation/moduleConfig/telemetry.ts

@ -4,7 +4,8 @@ import { IsBoolean, IsInt } from "class-validator";
export class TelemetryValidation export class TelemetryValidation
implements implements
Omit<Protobuf.ModuleConfig.ModuleConfig_TelemetryConfig, keyof Message> { Omit<Protobuf.ModuleConfig.ModuleConfig_TelemetryConfig, keyof Message>
{
@IsInt() @IsInt()
deviceUpdateInterval: number; deviceUpdateInterval: number;

Loading…
Cancel
Save