diff --git a/packages/web/public/i18n/locales/en/ui.json b/packages/web/public/i18n/locales/en/ui.json index 721050dde..46ffb0ed2 100644 --- a/packages/web/public/i18n/locales/en/ui.json +++ b/packages/web/public/i18n/locales/en/ui.json @@ -28,6 +28,15 @@ "title": "Firmware", "version": "v{{version}}", "buildDate": "Build date: {{date}}" + }, + "channelUtil": { + "title": "Ch Util" + }, + "airUtilTx": { + "title": "TX Airtime" + }, + "uptime": { + "title": "Uptime" } } }, diff --git a/packages/web/src/components/DeviceInfoPanel.tsx b/packages/web/src/components/DeviceInfoPanel.tsx index f962a382b..1a3ca9708 100644 --- a/packages/web/src/components/DeviceInfoPanel.tsx +++ b/packages/web/src/components/DeviceInfoPanel.tsx @@ -21,6 +21,7 @@ import type { DeviceMetrics } from "./types.ts"; import { Avatar } from "./UI/Avatar.tsx"; import { Button } from "./UI/Button.tsx"; import { Subtle } from "./UI/Typography/Subtle.tsx"; +import { Uptime } from "./generic/Uptime.tsx"; interface DeviceInfoPanelProps { isCollapsed: boolean; @@ -62,7 +63,7 @@ export const DeviceInfoPanel = ({ }: DeviceInfoPanelProps) => { const { t } = useTranslation(); const navigate = useNavigate({ from: "/" }); - const { batteryLevel, voltage } = deviceMetrics; + const { batteryLevel, voltage, channelUtilization, airUtilTx, uptimeSeconds} = deviceMetrics; const getStatusColor = (status?: ConnectionStatus): string => { if (!status) { @@ -117,6 +118,32 @@ export const DeviceInfoPanel = ({ icon: CpuIcon, value: firmwareVersion ?? t("unknown.notAvailable", "N/A"), }, + { + id: "channelUtil", + label: t("sidebar.deviceInfo.channelUtil.title"), + value: + channelUtilization !== undefined + ? `${(channelUtilization).toFixed(1)}%` + : "N/A", + }, + { + id: "airUtilTx", + label: t("sidebar.deviceInfo.airUtilTx.title"), + value: + airUtilTx !== undefined + ? `${(airUtilTx).toFixed(1)}%` + : "N/A", + }, + { + id: "uptimeSeconds", + label: t("sidebar.deviceInfo.uptime.title"), + value: + uptimeSeconds !== undefined ? ( + + ) : ( + "N/A" + ) + } ]; const actionButtons: ActionButtonConfig[] = [ diff --git a/packages/web/src/components/Sidebar.tsx b/packages/web/src/components/Sidebar.tsx index a5cf71a09..2243982b2 100644 --- a/packages/web/src/components/Sidebar.tsx +++ b/packages/web/src/components/Sidebar.tsx @@ -217,6 +217,9 @@ export const Sidebar = ({ children }: SidebarProps) => { typeof myNode.deviceMetrics?.voltage === "number" ? Math.abs(myNode.deviceMetrics?.voltage) : undefined, + channelUtilization: myNode.deviceMetrics?.channelUtilization, + airUtilTx: myNode.deviceMetrics?.airUtilTx, + uptimeSeconds: myNode.deviceMetrics?.uptimeSeconds, }} connectionStatus={activeConnection?.status} connectionName={activeConnection?.name} diff --git a/packages/web/src/components/generic/Uptime.tsx b/packages/web/src/components/generic/Uptime.tsx index 93467b52d..df6337674 100644 --- a/packages/web/src/components/generic/Uptime.tsx +++ b/packages/web/src/components/generic/Uptime.tsx @@ -5,9 +5,17 @@ export interface UptimeProps { const getUptime = (seconds: number): string => { const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); - const minutes = Math.floor(((seconds % 86400) % 3600) / 60); - const secondsLeft = Math.floor(((seconds % 86400) % 3600) % 60); - return `${days}d ${hours}h ${minutes}m ${secondsLeft}s`; + const minutes = Math.floor((seconds % 3600) / 60); + const secs = Math.floor(seconds % 60); + + const parts: string[] = []; + + if (days > 0) parts.push(`${days}d`); + if (hours > 0) parts.push(`${hours}h`); + if (minutes > 0) parts.push(`${minutes}m`); + if (secs > 0 || parts.length === 0) parts.push(`${secs}s`); + + return parts.join(" "); }; export const Uptime = ({ seconds }: UptimeProps) => { diff --git a/packages/web/src/components/types.ts b/packages/web/src/components/types.ts index 2fcf8d14c..29f88213d 100644 --- a/packages/web/src/components/types.ts +++ b/packages/web/src/components/types.ts @@ -1,4 +1,7 @@ export type DeviceMetrics = { batteryLevel?: number | null; voltage?: number | null; + channelUtilization?: number | null; + airUtilTx?: number | null; + uptimeSeconds?: number | null; }; diff --git a/packages/web/src/core/subscriptions.ts b/packages/web/src/core/subscriptions.ts index f20f5443d..bcccb0461 100644 --- a/packages/web/src/core/subscriptions.ts +++ b/packages/web/src/core/subscriptions.ts @@ -41,8 +41,14 @@ export const subscribeAll = ( } }); - connection.events.onTelemetryPacket.subscribe(() => { - // device.setMetrics(telemetryPacket); + connection.events.onTelemetryPacket.subscribe((packet) => { + const metrics = packet.data.variant.value; + const existing = nodeDB.getNode(packet.from); + nodeDB.addNode({ + ...(existing ?? {}), + num: packet.from, + deviceMetrics: { ...metrics }, + }); }); connection.events.onDeviceStatus.subscribe((status) => {