You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
190 lines
6.9 KiB
190 lines
6.9 KiB
import { useAppStore } from "../../core/stores/appStore.ts";
|
|
import { useDevice } from "../../core/stores/deviceStore.ts";
|
|
import {
|
|
Accordion,
|
|
AccordionContent,
|
|
AccordionItem,
|
|
AccordionTrigger,
|
|
} from "../UI/Accordion.tsx";
|
|
import {
|
|
Dialog,
|
|
DialogClose,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "../UI/Dialog.tsx";
|
|
import { Protobuf } from "@meshtastic/core";
|
|
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
|
|
import { DeviceImage } from "../generic/DeviceImage.tsx";
|
|
import { TimeAgo } from "../generic/TimeAgo.tsx";
|
|
import { Uptime } from "../generic/Uptime.tsx";
|
|
|
|
export interface NodeDetailsDialogProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
}
|
|
|
|
export const NodeDetailsDialog = ({
|
|
open,
|
|
onOpenChange,
|
|
}: NodeDetailsDialogProps) => {
|
|
const { nodes } = useDevice();
|
|
const { nodeNumDetails } = useAppStore();
|
|
const device: Protobuf.Mesh.NodeInfo = nodes.get(nodeNumDetails);
|
|
|
|
return device
|
|
? (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent>
|
|
<DialogClose />
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
Node Details for {device.user?.longName ?? "UNKNOWN"} (
|
|
{device.user?.shortName ?? "UNK"})
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
<DialogFooter>
|
|
<div className="w-full">
|
|
<DeviceImage
|
|
className="w-32 h-32 mx-auto rounded-lg border-4 border-slate-200 dark:border-slate-800"
|
|
deviceType={Protobuf.Mesh
|
|
.HardwareModel[device.user?.hwModel ?? 0]}
|
|
/>
|
|
<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">
|
|
Details:
|
|
</p>
|
|
<p>
|
|
Hardware:{" "}
|
|
{Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]}
|
|
</p>
|
|
<p>Node Number: {device.num}</p>
|
|
<p>Node HEX: !{numberToHexUnpadded(device.num)}</p>
|
|
<p>
|
|
Role: {Protobuf.Config.Config_DeviceConfig_Role[
|
|
device.user?.role ?? 0
|
|
]}
|
|
</p>
|
|
<p>
|
|
Last Heard: {device.lastHeard === 0
|
|
? (
|
|
"Never"
|
|
)
|
|
: <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>
|
|
{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>
|
|
)
|
|
: null}
|
|
|
|
{device.deviceMetrics
|
|
? (
|
|
<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
|
|
? (
|
|
<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
|
|
>
|
|
<AccordionItem className="AccordionItem" value="item-1">
|
|
<AccordionTrigger>
|
|
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
|
|
All Raw Metrics:
|
|
</p>
|
|
</AccordionTrigger>
|
|
<AccordionContent className="overflow-x-scroll">
|
|
<pre className="text-xs w-full">
|
|
{JSON.stringify(device, null, 2)}
|
|
</pre>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
</Accordion>
|
|
</div>
|
|
)
|
|
: null}
|
|
</div>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
: null;
|
|
};
|
|
|