From 176d554ef9bfb1d35be67e3ea12784c8e5c2bd0f Mon Sep 17 00:00:00 2001 From: Jeremy Gallant <8975765+philon-@users.noreply.github.com> Date: Mon, 11 Aug 2025 03:58:26 +0200 Subject: [PATCH] Improve NodeDetailsDialog UI and add security info (#770) * Improve NodeDetailsDialog UI and add security info Refactored NodeDetailsDialog to use tables for better layout and readability, added a security section displaying public key and verification status, and included messageable status. Updated i18n files with new keys and improved battery level formatting. Fixed logic in Nodes page for handling location packets and improved hardware model sorting. * Update NodeDetailsDialog.tsx --- .../web/public/i18n/locales/en/common.json | 4 +- .../web/public/i18n/locales/en/dialog.json | 9 +- .../NodeDetailsDialog/NodeDetailsDialog.tsx | 219 ++++++++++++------ packages/web/src/pages/Nodes/index.tsx | 8 +- 4 files changed, 160 insertions(+), 80 deletions(-) diff --git a/packages/web/public/i18n/locales/en/common.json b/packages/web/public/i18n/locales/en/common.json index d6cd2a17..e0e95049 100644 --- a/packages/web/public/i18n/locales/en/common.json +++ b/packages/web/public/i18n/locales/en/common.json @@ -107,5 +107,7 @@ "managed": "At least one admin key is requred if the node is managed.", "key": "Key is required." } - } + }, + "yes": "Yes", + "no": "No" } diff --git a/packages/web/public/i18n/locales/en/dialog.json b/packages/web/public/i18n/locales/en/dialog.json index c343b369..feec9ebb 100644 --- a/packages/web/public/i18n/locales/en/dialog.json +++ b/packages/web/public/i18n/locales/en/dialog.json @@ -93,7 +93,7 @@ "deviceMetrics": "Device Metrics:", "hardware": "Hardware: ", "lastHeard": "Last Heard: ", - "nodeHexPrefix": "Node Hex: !", + "nodeHexPrefix": "Node Hex: ", "nodeNumber": "Node Number: ", "position": "Position:", "role": "Role: ", @@ -102,7 +102,12 @@ "title": "Node Details for {{identifier}}", "ignoreNode": "Ignore node", "removeNode": "Remove node", - "unignoreNode": "Unignore node" + "unignoreNode": "Unignore node", + "security": "Security:", + "publicKey": "Public Key: ", + "messageable": "Messageable: ", + "KeyManuallyVerifiedTrue": "Public Key has been manually verified", + "KeyManuallyVerifiedFalse": "Public Key is not manually verified" }, "pkiBackup": { "loseKeysWarning": "If you lose your keys, you will need to reset your device.", diff --git a/packages/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx b/packages/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx index eff2f756..ba95bce9 100644 --- a/packages/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx +++ b/packages/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx @@ -33,6 +33,7 @@ import { cn } from "@core/utils/cn.ts"; import { Protobuf } from "@meshtastic/core"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { useNavigate } from "@tanstack/react-router"; +import { fromByteArray } from "base64-js"; import { BellIcon, BellOffIcon, @@ -167,7 +168,8 @@ export const NodeDetailsDialog = ({ key: "batteryLevel", label: t("nodeDetails.batteryLevel"), value: node.deviceMetrics?.batteryLevel, - format: (val: number) => `${val.toFixed(2)}%`, + format: (val: number) => + val === 101 ? t("batteryStatus.pluggedIn") : `${val.toFixed(2)}%`, }, { key: "voltage", @@ -177,6 +179,9 @@ export const NodeDetailsDialog = ({ }, ]; + const sectionClassName = + "text-slate-900 dark:text-slate-100 bg-slate-100 dark:bg-slate-800 p-4 rounded-lg mt-3"; + return ( @@ -192,7 +197,7 @@ export const NodeDetailsDialog = ({ -
+
-
+
+

+ {t("nodeDetails.security")} +

+ + + + + + + + + + + +
{t("nodeDetails.publicKey")} +
+                          {node.user?.publicKey &&
+                          node.user?.publicKey.length > 0
+                            ? fromByteArray(node.user.publicKey)
+                            : t("unknown.longName")}
+                        
+
+ {node.isKeyManuallyVerified + ? t("nodeDetails.KeyManuallyVerifiedTrue") + : t("nodeDetails.KeyManuallyVerifiedFalse")} +
+
+ +

{t("nodeDetails.position")}

{node.position ? ( - <> - {node.position.latitudeI && node.position.longitudeI && ( -

- {t("locationResponse.coordinates")} - - {node.position.latitudeI / 1e7},{" "} - {node.position.longitudeI / 1e7} - -

- )} - {node.position.altitude && ( -

- {t("locationResponse.altitude")} - {node.position.altitude} - {t("unit.meter.one")} -

- )} - + + + {node.position.latitudeI && node.position.longitudeI && ( + + + + + )} + {node.position.altitude && ( + + + + + )} + +
{t("locationResponse.coordinates")} + + {node.position.latitudeI / 1e7},{" "} + {node.position.longitudeI / 1e7} + +
{t("locationResponse.altitude")} + {node.position.altitude} + {t("unit.meter.suffix")} +
) : ( -

{t("unknown.shortName")}

+

{t("unknown.longName")}

)}