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;