diff --git a/src/components/Dialog/DialogManager.tsx b/src/components/Dialog/DialogManager.tsx index e1769751..d571ffcb 100644 --- a/src/components/Dialog/DialogManager.tsx +++ b/src/components/Dialog/DialogManager.tsx @@ -6,6 +6,8 @@ import { QRDialog } from "@components/Dialog/QRDialog.tsx"; import { RebootDialog } from "@components/Dialog/RebootDialog.tsx"; import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx"; import { useDevice } from "@core/stores/deviceStore.ts"; +import type { JSX } from "react"; +import { NodeDetailsDialog } from "./NodeDetailsDialog"; export const DialogManager = (): JSX.Element => { const { channels, config, dialog, setDialogOpen } = useDevice(); @@ -56,6 +58,12 @@ export const DialogManager = (): JSX.Element => { setDialogOpen("pkiBackup", open); }} /> + { + setDialogOpen("nodeDetails", open); + }} + /> ); }; diff --git a/src/components/Dialog/NodeDetailsDialog.tsx b/src/components/Dialog/NodeDetailsDialog.tsx new file mode 100644 index 00000000..13a9782d --- /dev/null +++ b/src/components/Dialog/NodeDetailsDialog.tsx @@ -0,0 +1,191 @@ +import { useAppStore } from "@app/core/stores/appStore"; +import { useDevice } from "@app/core/stores/deviceStore"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@components/UI/Dialog.tsx"; +import { Protobuf } from "@meshtastic/js"; +import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; +import { useEffect } from "react"; +import { useState } from "react"; +import { DeviceImage } from "../generic/DeviceImage"; +import { TimeAgo } from "../generic/TimeAgo"; +import { Uptime } from "../generic/Uptime"; + +export interface NodeDetailsDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export const NodeDetailsDialog = ({ + open, + onOpenChange, +}: NodeDetailsDialogProps) => { + const { nodes } = useDevice(); + const { nodeNumDetails } = useAppStore(); + const [device, setDevice] = useState(null); + + useEffect(() => { + if (!nodeNumDetails) return; + setDevice(nodes.get(nodeNumDetails)); + }, [nodeNumDetails, nodes]); + + return device ? ( + + + + + Node Details for {device.user?.longName ?? "UNKNOWN"} ( + {device.user?.shortName ?? "UNK"}) + + + +
+ +
+

+ Details: +

+

+ Hardware:{" "} + {Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]} +

+

Node Number: {device.num}

+

Node HEX: !{numberToHexUnpadded(device.num)}

+

+ Role:{" "} + { + Protobuf.Config.Config_DeviceConfig_Role[ + device.user?.role ?? 0 + ] + } +

+

+ Last Heard:{" "} + {device.lastHeard === 0 ? ( + "Never" + ) : ( + + )} +

+
+ + {device.position ? ( +
+

+ Position: +

+ {device.position.latitudeI && device.position.longitudeI ? ( +

+ Coordinates:{" "} + + {device.position.latitudeI / 1e7},{" "} + {device.position.longitudeI / 1e7} + +

+ ) : null} + {device.position.altitude ? ( +

Altitude: {device.position.altitude}m

+ ) : null} +
+ ) : null} + + {device.deviceMetrics ? ( +
+

+ Device Metrics: +

+ {device.deviceMetrics.airUtilTx ? ( +

+ Air TX utilization:{" "} + {device.deviceMetrics.airUtilTx.toFixed(2)}% +

+ ) : null} + {device.deviceMetrics.channelUtilization ? ( +

+ Channel utilization:{" "} + {device.deviceMetrics.channelUtilization.toFixed(2)}% +

+ ) : null} + {device.deviceMetrics.batteryLevel ? ( +

+ Battery level:{" "} + {device.deviceMetrics.batteryLevel.toFixed(2)}% +

+ ) : null} + {device.deviceMetrics.voltage ? ( +

Voltage: {device.deviceMetrics.voltage.toFixed(2)}V

+ ) : null} + {device.deviceMetrics.uptimeSeconds ? ( +

+ Uptime:{" "} + +

+ ) : null} +
+ ) : null} + + {device.environmentMetrics ? ( +
+

+ Environment Metrics: +

+ {device.deviceMetrics.airUtilTx ? ( +

+ Air TX utilization:{" "} + {device.deviceMetrics.airUtilTx.toFixed(2)}% +

+ ) : null} + {device.deviceMetrics.channelUtilization ? ( +

+ Channel utilization:{" "} + {device.deviceMetrics.channelUtilization.toFixed(2)}% +

+ ) : null} + {device.deviceMetrics.batteryLevel ? ( +

+ Battery level:{" "} + {device.deviceMetrics.batteryLevel.toFixed(2)}% +

+ ) : null} + {device.deviceMetrics.voltage ? ( +

Voltage: {device.deviceMetrics.voltage.toFixed(2)}V

+ ) : null} + {device.deviceMetrics.uptimeSeconds ? ( +

+ Uptime:{" "} + +

+ ) : null} +
+ ) : null} + + {device ? ( +
+

+ All Raw Metrics: +

+
+                  {JSON.stringify(device, null, 2)}
+                
+
+ ) : null} +
+
+
+
+ ) : null; +}; diff --git a/src/components/Dialog/NodeOptionsDialog.tsx b/src/components/Dialog/NodeOptionsDialog.tsx index 06a3f0b8..83a01426 100644 --- a/src/components/Dialog/NodeOptionsDialog.tsx +++ b/src/components/Dialog/NodeOptionsDialog.tsx @@ -25,7 +25,7 @@ export const NodeOptionsDialog = ({ onOpenChange, }: NodeOptionsDialogProps): JSX.Element => { const { setDialogOpen, connection } = useDevice(); - const { setNodeNumToBeRemoved } = useAppStore(); + const { setNodeNumToBeRemoved, setNodeNumDetails } = useAppStore(); const longName = node?.user?.longName ?? (node ? `!${numberToHexUnpadded(node?.num)}` : "Unknown"); @@ -85,6 +85,16 @@ export const NodeOptionsDialog = ({ Remove +
+ +
diff --git a/src/components/UI/Dialog.tsx b/src/components/UI/Dialog.tsx index 019b8142..d989b842 100644 --- a/src/components/UI/Dialog.tsx +++ b/src/components/UI/Dialog.tsx @@ -44,7 +44,7 @@ const DialogContent = React.forwardRef< void; addRasterSource: (source: RasterSource) => void; @@ -42,6 +43,7 @@ interface AppState { setNodeNumToBeRemoved: (nodeNum: number) => void; setAccent: (color: AccentColor) => void; setConnectDialogOpen: (open: boolean) => void; + setNodeNumDetails: (nodeNum: number) => void; } export const useAppStore = create()((set) => ({ @@ -57,6 +59,7 @@ export const useAppStore = create()((set) => ({ accent: "orange", connectDialogOpen: false, nodeNumToBeRemoved: 0, + nodeNumDetails: 0, setRasterSources: (sources: RasterSource[]) => { set( @@ -124,4 +127,8 @@ export const useAppStore = create()((set) => ({ }), ); }, + setNodeNumDetails: (nodeNum) => + set((state) => ({ + nodeNumDetails: nodeNum, + })), })); diff --git a/src/core/stores/deviceStore.ts b/src/core/stores/deviceStore.ts index bd407611..a4ad7b69 100644 --- a/src/core/stores/deviceStore.ts +++ b/src/core/stores/deviceStore.ts @@ -26,7 +26,8 @@ export type DialogVariant = | "reboot" | "deviceName" | "nodeRemoval" - | "pkiBackup"; + | "pkiBackup" + | "nodeDetails"; export interface Device { id: number; @@ -62,6 +63,7 @@ export interface Device { deviceName: boolean; nodeRemoval: boolean; pkiBackup: boolean; + nodeDetails: boolean; }; setStatus: (status: Types.DeviceStatusEnum) => void; @@ -145,6 +147,7 @@ export const useDeviceStore = create((set, get) => ({ deviceName: false, nodeRemoval: false, pkiBackup: false, + nodeDetails: false, }, pendingSettingsChanges: false, messageDraft: "",