From ad83bc7979f00f102f0b61edf769e72ba9688674 Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Sat, 4 Apr 2026 14:29:31 -0700 Subject: [PATCH] Show telemetry inline --- .../settings/SettingsDatabaseSection.tsx | 89 +++++++++++++++---- frontend/src/utils/messageParser.ts | 1 - 2 files changed, 70 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/settings/SettingsDatabaseSection.tsx b/frontend/src/components/settings/SettingsDatabaseSection.tsx index 0ae6c6e..1bb2b12 100644 --- a/frontend/src/components/settings/SettingsDatabaseSection.tsx +++ b/frontend/src/components/settings/SettingsDatabaseSection.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { Input } from '../ui/input'; import { Label } from '../ui/label'; import { Button } from '../ui/button'; @@ -7,7 +7,13 @@ import { toast } from '../ui/sonner'; import { api } from '../../api'; import { formatTime } from '../../utils/messageParser'; import { BulkDeleteContactsModal } from './BulkDeleteContactsModal'; -import type { AppSettings, AppSettingsUpdate, Contact, HealthStatus } from '../../types'; +import type { + AppSettings, + AppSettingsUpdate, + Contact, + HealthStatus, + TelemetryHistoryEntry, +} from '../../types'; export function SettingsDatabaseSection({ appSettings, @@ -48,11 +54,35 @@ export function SettingsDatabaseSection({ const [busy, setBusy] = useState(false); const [error, setError] = useState(null); + const [latestTelemetry, setLatestTelemetry] = useState< + Record + >({}); + const telemetryFetchedRef = useRef(false); + useEffect(() => { setAutoDecryptOnAdvert(appSettings.auto_decrypt_dm_on_advert); setDiscoveryBlockedTypes(appSettings.discovery_blocked_types ?? []); }, [appSettings]); + useEffect(() => { + if (trackedTelemetryRepeaters.length === 0 || telemetryFetchedRef.current) return; + telemetryFetchedRef.current = true; + let cancelled = false; + const fetches = trackedTelemetryRepeaters.map((key) => + api.repeaterTelemetryHistory(key).then( + (history) => [key, history.length > 0 ? history[history.length - 1] : null] as const, + () => [key, null] as const + ) + ); + Promise.all(fetches).then((entries) => { + if (cancelled) return; + setLatestTelemetry(Object.fromEntries(entries)); + }); + return () => { + cancelled = true; + }; + }, [trackedTelemetryRepeaters]); + const handleCleanup = async () => { const days = parseInt(retentionDays, 10); if (isNaN(days) || days < 1) { @@ -242,28 +272,49 @@ export function SettingsDatabaseSection({ No repeaters are being tracked. Enable tracking from a repeater's dashboard.

) : ( -
+
{trackedTelemetryRepeaters.map((key) => { const contact = contacts.find((c) => c.public_key === key); const displayName = contact?.name ?? key.slice(0, 12); + const snap = latestTelemetry[key]; + const d = snap?.data; return ( -
-
- {displayName} - - {key.slice(0, 12)} - +
+
+
+ {displayName} + + {key.slice(0, 12)} + +
+ {onToggleTrackedTelemetry && ( + + )}
- {onToggleTrackedTelemetry && ( - - )} + {d ? ( +
+ {d.battery_volts?.toFixed(2)}V + noise {d.noise_floor_dbm} dBm + + rx {d.packets_received != null ? d.packets_received.toLocaleString() : '?'} + + + tx {d.packets_sent != null ? d.packets_sent.toLocaleString() : '?'} + + checked {formatTime(snap.timestamp)} +
+ ) : snap === null ? ( +
+ No telemetry recorded yet +
+ ) : null}
); })} diff --git a/frontend/src/utils/messageParser.ts b/frontend/src/utils/messageParser.ts index f41ebdb..90df106 100644 --- a/frontend/src/utils/messageParser.ts +++ b/frontend/src/utils/messageParser.ts @@ -26,7 +26,6 @@ export interface HashtagChannelReference { end: number; } - export function findLinkedChannelReferences(text: string): HashtagChannelReference[] { const references: HashtagChannelReference[] = []; let match: RegExpExecArray | null;