Harden v0.9.75 wormhole node sync and telemetry panels

Add Tor/onion runtime wiring and faster Infonet node status refresh.

Keep node bootstrap state clearer across Docker and local runtimes.

Use selected aircraft trail history for cumulative tracked-aircraft emissions.
This commit is contained in:
BigBodyCobain
2026-05-06 14:04:16 -06:00
parent 8926e08009
commit b8ac0fb9e7
15 changed files with 698 additions and 201 deletions
@@ -11,7 +11,6 @@ import {
} from '@/mesh/infonetEconomyClient';
import { generateNodeKeys, getNodeIdentity } from '@/mesh/meshIdentity';
import {
DEFAULT_INFONET_SEED_URL,
fetchInfonetNodeStatusSnapshot,
setInfonetNodeEnabled,
type InfonetNodeStatusSnapshot,
@@ -57,9 +56,12 @@ export default function BootstrapView({ marketId, onBack }: BootstrapViewProps)
const nodeEnabled = Boolean(nodeStatus?.node_enabled);
const nodeMode = String(nodeStatus?.node_mode || 'participant').toUpperCase();
const syncOutcome = String(nodeStatus?.sync_runtime?.last_outcome || 'idle').toLowerCase();
const seedPeerCount = Number(nodeStatus?.bootstrap?.default_sync_peer_count || 0);
const seedPeerCount = Number(
nodeStatus?.bootstrap?.bootstrap_seed_peer_count ?? nodeStatus?.bootstrap?.default_sync_peer_count ?? 0,
);
const syncPeerCount = Number(nodeStatus?.bootstrap?.sync_peer_count || 0);
const lastPeerUrl = String(nodeStatus?.sync_runtime?.last_peer_url || '').trim();
const privateTransportRequired = Boolean(nodeStatus?.private_transport_required);
const toggleNode = useCallback(async (enabled: boolean) => {
setNodeToggleBusy(true);
@@ -146,8 +148,10 @@ export default function BootstrapView({ marketId, onBack }: BootstrapViewProps)
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-2 text-xs">
<div>
<div className="text-gray-500">Default Seed</div>
<div className="text-cyan-300 font-mono break-all">{DEFAULT_INFONET_SEED_URL}</div>
<div className="text-gray-500">Transport</div>
<div className="text-cyan-300 font-mono break-all">
{privateTransportRequired ? 'ONION / RNS ONLY' : 'CLEARNET DEV OVERRIDE'}
</div>
</div>
<div>
<div className="text-gray-500">Local Node</div>
@@ -158,15 +162,15 @@ export default function BootstrapView({ marketId, onBack }: BootstrapViewProps)
<div>
<div className="text-gray-500">Sync Path</div>
<div className="text-white font-mono">
{syncPeerCount} peers / {seedPeerCount} default
{syncPeerCount} peers / {seedPeerCount} seeds
</div>
</div>
</div>
<div className="mt-3 flex flex-col md:flex-row md:items-center gap-3">
<div className="flex-1 text-[11px] text-gray-500 leading-relaxed">
{nodeEnabled
? `Public chain sync is ${syncOutcome || 'active'}${lastPeerUrl ? ` via ${lastPeerUrl}` : ''}.`
: 'Start a local participant node to pull from the default seed and help carry the public Infonet chain while this backend is running.'}
? `Infonet sync is ${syncOutcome || 'active'}${lastPeerUrl ? ` via ${lastPeerUrl}` : ''}.`
: 'Start a local participant node to sync through available Wormhole onion/RNS peers while this backend is running.'}
</div>
<button
type="button"
@@ -3,6 +3,11 @@
import React, { useEffect } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import { X } from 'lucide-react';
import {
fetchInfonetNodeStatusSnapshot,
setInfonetNodeEnabled,
startTorHiddenService,
} from '@/mesh/controlPlaneStatusClient';
import InfonetShell from './InfonetShell';
interface InfonetTerminalProps {
@@ -28,6 +33,33 @@ export default function InfonetTerminal({
return () => window.removeEventListener('keydown', handler);
}, [isOpen, onClose]);
useEffect(() => {
if (!isOpen) return;
let cancelled = false;
const connectParticipantNode = async () => {
try {
const nodeStatus = await fetchInfonetNodeStatusSnapshot(true).catch(() => null);
if (cancelled || nodeStatus?.node_enabled) return;
const torStatus = await startTorHiddenService().catch(() => null);
if (cancelled || !torStatus?.running || !torStatus?.onion_address) return;
await setInfonetNodeEnabled(true);
if (!cancelled) {
await fetchInfonetNodeStatusSnapshot(true).catch(() => null);
}
} catch {
// Remote/shared viewers may not have local-operator rights. Leave manual controls intact.
}
};
void connectParticipantNode();
return () => {
cancelled = true;
};
}, [isOpen]);
return (
<AnimatePresence>
{isOpen && (
+57 -4
View File
@@ -7,6 +7,7 @@ import React, { useEffect, useRef, useCallback } from 'react';
import WikiImage from '@/components/WikiImage';
import type { SelectedEntity, RegionDossier, FimiData } from "@/types/dashboard";
import { useDataKeys } from '@/hooks/useDataStore';
import { API_BASE } from '@/lib/api';
import { lookupShodanHost } from '@/lib/shodanClient';
import type { ShodanHost } from '@/types/shodan';
@@ -291,6 +292,8 @@ function estimateObservedEmissions(flight: any): {
const fuelGph = Number(flight?.emissions?.fuel_gph);
const co2KgPerHour = Number(flight?.emissions?.co2_kg_per_hour);
const trail = Array.isArray(flight?.trail) ? (flight.trail as FlightTrailPoint[]) : [];
const isTrackedAircraft = flight?.type === 'tracked_flight' || Boolean(flight?.alert_category);
const minimumObservedHours = isTrackedAircraft ? 1 / 60 : 5 / 60;
if (!Number.isFinite(fuelGph) || !Number.isFinite(co2KgPerHour)) {
return null;
}
@@ -301,7 +304,7 @@ function estimateObservedEmissions(flight: any): {
.sort((a, b) => a - b);
if (timestamps.length >= 2) {
const elapsedHours = (timestamps[timestamps.length - 1] - timestamps[0]) / 3600;
if (Number.isFinite(elapsedHours) && elapsedHours >= 5 / 60) {
if (Number.isFinite(elapsedHours) && elapsedHours >= minimumObservedHours) {
let distance = 0;
let previous: { lat: number; lng: number } | null = null;
for (const point of trail) {
@@ -336,7 +339,7 @@ function estimateObservedEmissions(flight: any): {
) {
const flownNm = distanceNm(origin, current);
const elapsedHours = flownNm / speedKnots;
if (Number.isFinite(elapsedHours) && elapsedHours >= 5 / 60 && elapsedHours <= 18) {
if (Number.isFinite(elapsedHours) && elapsedHours >= minimumObservedHours && elapsedHours <= 18) {
return {
fuelGallons: Math.round(fuelGph * elapsedHours),
co2Kg: Math.round(co2KgPerHour * elapsedHours),
@@ -405,6 +408,7 @@ function NewsFeedInner({ selectedEntity, regionDossier, regionDossierLoading, on
'airports', 'last_updated', 'threat_level',
] as const);
const [isMinimized, setIsMinimized] = useState(false);
const [selectedFlightTrail, setSelectedFlightTrail] = useState<FlightTrailPoint[]>([]);
const [expandedIndexes, setExpandedIndexes] = useState<number[]>([]);
const [fimiExpanded, setFimiExpanded] = useState(false);
const [aiSummaryOpen, setAiSummaryOpen] = useState(false);
@@ -453,6 +457,53 @@ function NewsFeedInner({ selectedEntity, regionDossier, regionDossierLoading, on
return flight?.model;
})();
const { imgUrl: aircraftImgUrl, wikiUrl: aircraftWikiUrl, loading: aircraftImgLoading } = useAircraftImage(selectedFlightModel);
useEffect(() => {
const flightSelectionTypes = new Set([
'flight',
'commercial_flight',
'private_flight',
'private_ga',
'private_jet',
'military_flight',
'tracked_flight',
]);
if (!selectedEntity || !flightSelectionTypes.has(selectedEntity.type)) {
setSelectedFlightTrail([]);
return;
}
const trailId = String(selectedEntity.id || '').trim();
if (!trailId) {
setSelectedFlightTrail([]);
return;
}
let cancelled = false;
fetch(`${API_BASE}/api/trail/flight/${encodeURIComponent(trailId)}`, { cache: 'no-store' })
.then((res) => (res.ok ? res.json() : null))
.then((payload) => {
if (cancelled) return;
const trail = Array.isArray(payload?.trail) ? payload.trail as FlightTrailPoint[] : [];
setSelectedFlightTrail(trail);
})
.catch(() => {
if (!cancelled) setSelectedFlightTrail([]);
});
return () => {
cancelled = true;
};
}, [selectedEntity?.id, selectedEntity?.type]);
const withSelectedTrail = useCallback((flight: any) => {
if (!flight || selectedFlightTrail.length < 2) return flight;
const selectedId = String(selectedEntity?.id || '').trim().toLowerCase();
const flightId = String(flight.icao24 || '').trim().toLowerCase();
if (!selectedId || !flightId || selectedId !== flightId) return flight;
return { ...flight, trail: selectedFlightTrail };
}, [selectedEntity?.id, selectedFlightTrail]);
const [shodanDetail, setShodanDetail] = useState<ShodanHost | null>(null);
const [shodanLoading, setShodanLoading] = useState(false);
const [shodanError, setShodanError] = useState<string | null>(null);
@@ -666,6 +717,7 @@ function NewsFeedInner({ selectedEntity, regionDossier, regionDossierLoading, on
if (selectedEntity?.type === 'tracked_flight') {
const flight = data?.tracked_flights?.find((f: any) => f.icao24 === selectedEntity.id);
if (flight) {
const flightForEmissions = withSelectedTrail(flight);
const callsign = flight.callsign || "UNKNOWN";
const alertColorMap: Record<string, string> = {
'#ff1493': 'text-[#ff1493]', pink: 'text-[#ff1493]', red: 'text-red-400', yellow: 'text-yellow-400',
@@ -851,7 +903,7 @@ function NewsFeedInner({ selectedEntity, regionDossier, regionDossierLoading, on
<span className={`text-xs font-bold ${flight.squawk === '7700' ? 'text-red-400 animate-pulse' : flight.squawk === '7600' ? 'text-yellow-400' : 'text-[var(--text-primary)]'}`}>{flight.squawk}{flight.squawk === '7700' ? ' ⚠ EMERGENCY' : flight.squawk === '7600' ? ' COMMS LOST' : ''}</span>
</div>
)}
<EmissionsEstimateBlock flight={flight} />
<EmissionsEstimateBlock flight={flightForEmissions} />
{flight.alert_link && (
<div className="flex justify-between items-center border-b border-[var(--border-primary)] pb-2">
<span className="text-[var(--text-muted)] text-[10px]">REFERENCE</span>
@@ -903,6 +955,7 @@ function NewsFeedInner({ selectedEntity, regionDossier, regionDossierLoading, on
const flight = flightsList?.find((f: any) => f.icao24 === selectedEntity.id);
if (flight) {
const flightForEmissions = withSelectedTrail(flight);
const callsign = flight.callsign || "UNKNOWN";
let airline = "UNKNOWN";
const isPrivateFlight = selectedEntity.type === 'private_flight' || selectedEntity.type === 'private_jet';
@@ -1107,7 +1160,7 @@ function NewsFeedInner({ selectedEntity, regionDossier, regionDossierLoading, on
<span className="text-[var(--text-muted)] text-[10px]">ROUTE</span>
<span className="text-cyan-400 text-xs font-bold">{flight.origin_name !== "UNKNOWN" ? `[${flight.origin_name}] → [${flight.dest_name}]` : "UNKNOWN"}</span>
</div>
<EmissionsEstimateBlock flight={flight} />
<EmissionsEstimateBlock flight={flightForEmissions} />
{flight.icao24 && (
<div className="flex justify-between items-center border-b border-[var(--border-primary)] pb-2">
<span className="text-[var(--text-muted)] text-[10px]">FLIGHT RECORD</span>
+13 -9
View File
@@ -33,8 +33,8 @@ import {
import { purgeBrowserContactGraph, purgeBrowserSigningMaterial, setSecureModeCached, getNodeIdentity, generateNodeKeys } from '@/mesh/meshIdentity';
import { purgeBrowserDmState } from '@/mesh/meshDmWorkerClient';
import {
DEFAULT_INFONET_SEED_URL,
fetchInfonetNodeStatusSnapshot,
startTorHiddenService,
type InfonetNodeStatusSnapshot,
} from '@/mesh/controlPlaneStatusClient';
import {
@@ -263,6 +263,10 @@ export default function TopRightControls({
await generateNodeKeys();
}
setActivatingPhase('peers');
const torStatus = await startTorHiddenService();
if (!torStatus?.running || !torStatus?.onion_address) {
throw new Error(torStatus?.detail || 'Tor onion service did not start');
}
}
const res = await controlPlaneFetch('/api/settings/node', {
@@ -833,9 +837,9 @@ export default function TopRightControls({
: activatingPhase === 'peers' ? 'text-cyan-300'
: 'text-green-300'
}>
{activatingPhase === 'keys' ? 'Connecting to default seed...'
: activatingPhase === 'peers' ? 'Connecting to default seed...'
: 'Default seed connected'}
{activatingPhase === 'keys' ? 'Preparing onion transport...'
: activatingPhase === 'peers' ? 'Finding bootstrap peers...'
: 'Bootstrap peers ready'}
</span>
</div>
{/* Step: Sync chain */}
@@ -899,7 +903,7 @@ export default function TopRightControls({
<div className="border border-cyan-500/20 bg-cyan-950/10 px-4 py-4 text-[10px] font-mono text-cyan-100 leading-[1.8]">
Do you want to activate a node on this install?
<div className="mt-2 text-[9px] text-cyan-200/70 normal-case tracking-normal">
This turns on your local participant node and lets this install keep syncing the public Infonet chain from <span className="text-cyan-300">{DEFAULT_INFONET_SEED_URL}</span>.
This turns on your local participant node and syncs Infonet only through available Wormhole onion/RNS peers. Clearnet bootstrap is disabled by default.
</div>
</div>
{(bootstrapFailed || nodeStatusError || nodeToggleError) && (
@@ -930,10 +934,10 @@ export default function TopRightControls({
<div className="text-cyan-300 tracking-[0.18em]">BY CONTINUING YOU AGREE:</div>
<ul className="mt-3 space-y-2 list-disc pl-5">
<li>This install can keep a local copy of the public Infonet chain.</li>
<li>Fresh installs pull from the bundled default seed at {DEFAULT_INFONET_SEED_URL}.</li>
<li>Participant-node sync is public-facing unless you separately use obfuscated-lane features.</li>
<li>Your backend may sync with configured or bundled bootstrap peers in the background.</li>
<li>Wormhole provides gates (transitional private lane) and Dead Drop / DM (stronger private lane) as separate postures.</li>
<li>Fresh installs do not use a clearnet Infonet seed.</li>
<li>Participant-node sync requires an onion/RNS peer through Wormhole.</li>
<li>Your backend may sync with configured private bootstrap peers in the background.</li>
<li>Wormhole keeps Infonet, gates, Dead Drop, and DM traffic on the obfuscated lane.</li>
</ul>
</div>
<div className="text-[11px] font-mono uppercase tracking-[0.2em] text-cyan-300/80">