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

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;
};