mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-06-04 13:28:13 +02:00
Harden infonet control surfaces
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -7,16 +7,17 @@ import { fetchInfonetNodeStatusSnapshot } from '@/mesh/controlPlaneStatusClient'
|
||||
interface Stats {
|
||||
meshtastic: number;
|
||||
aprs: number;
|
||||
infonetNodes: number;
|
||||
ledgerNodes: number;
|
||||
infonetEvents: number;
|
||||
syncPeers: number;
|
||||
seedPeers: number;
|
||||
nodeEnabled: boolean;
|
||||
syncOutcome: string;
|
||||
}
|
||||
|
||||
const EMPTY: Stats = {
|
||||
meshtastic: 0, aprs: 0, infonetNodes: 0, infonetEvents: 0,
|
||||
syncPeers: 0, nodeEnabled: false, syncOutcome: 'offline',
|
||||
meshtastic: 0, aprs: 0, ledgerNodes: 0, infonetEvents: 0,
|
||||
syncPeers: 0, seedPeers: 0, nodeEnabled: false, syncOutcome: 'offline',
|
||||
};
|
||||
|
||||
export default function NetworkStats() {
|
||||
@@ -32,22 +33,21 @@ export default function NetworkStats() {
|
||||
fetchInfonetNodeStatusSnapshot(true).catch(() => null),
|
||||
]);
|
||||
if (!alive) return;
|
||||
const knownNodes = Number(infonet?.known_nodes || 0);
|
||||
const authorNodes = Number(infonet?.author_nodes ?? infonet?.known_nodes ?? 0);
|
||||
const registeredNodes = Number(infonet?.registered_nodes || 0);
|
||||
const syncPeerCount = Number(infonet?.bootstrap?.sync_peer_count || 0);
|
||||
const defaultSyncPeerCount = Number(infonet?.bootstrap?.default_sync_peer_count || 0);
|
||||
const lastPeerUrl = String(infonet?.sync_runtime?.last_peer_url || '').trim();
|
||||
const visibleInfonetNodes = Math.max(
|
||||
knownNodes,
|
||||
syncPeerCount,
|
||||
defaultSyncPeerCount,
|
||||
lastPeerUrl ? 1 : 0,
|
||||
const seedPeerCount = Number(
|
||||
infonet?.bootstrap?.bootstrap_seed_peer_count
|
||||
?? infonet?.bootstrap?.default_sync_peer_count
|
||||
?? 0,
|
||||
);
|
||||
setStats({
|
||||
meshtastic: Number(channelsRes?.total_live || channelsRes?.total_nodes || meshRes?.signal_counts?.meshtastic || 0),
|
||||
aprs: Number(meshRes?.signal_counts?.aprs || 0),
|
||||
infonetNodes: visibleInfonetNodes,
|
||||
ledgerNodes: Math.max(authorNodes, registeredNodes),
|
||||
infonetEvents: Number(infonet?.total_events || 0),
|
||||
syncPeers: syncPeerCount,
|
||||
seedPeers: seedPeerCount,
|
||||
nodeEnabled: Boolean(infonet?.node_enabled),
|
||||
syncOutcome: String(infonet?.sync_runtime?.last_outcome || 'offline').toLowerCase(),
|
||||
});
|
||||
@@ -74,11 +74,21 @@ export default function NetworkStats() {
|
||||
<span className="text-gray-700">|</span>
|
||||
<span>APRS <span className={stats.aprs > 0 ? 'text-green-400' : 'text-gray-600'}>{stats.aprs.toLocaleString()}</span></span>
|
||||
<span className="text-gray-700">|</span>
|
||||
<span>INFONET NODES <span className="text-white">{stats.infonetNodes}</span></span>
|
||||
<span title="Distinct identities this node has seen on the accepted Infonet ledger. This is not a live user count.">
|
||||
LEDGER NODES <span className="text-white">{stats.ledgerNodes}</span>
|
||||
</span>
|
||||
<span className="text-gray-700">|</span>
|
||||
<span>EVENTS <span className="text-white">{stats.infonetEvents}</span></span>
|
||||
<span className="text-gray-700">|</span>
|
||||
<span>PEERS <span className="text-white">{stats.syncPeers}</span></span>
|
||||
<span title="Configured peers this node pulls from. Usually this is just the seed unless another device is added as a sync peer.">
|
||||
SYNC PEERS <span className="text-white">{stats.syncPeers}</span>
|
||||
</span>
|
||||
{stats.seedPeers > stats.syncPeers ? (
|
||||
<>
|
||||
<span className="text-gray-700">|</span>
|
||||
<span title="Bootstrap seed peers available from config or manifest.">SEEDS <span className="text-white">{stats.seedPeers}</span></span>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -305,6 +305,42 @@ function createPublicMeshAddress(): string {
|
||||
return `!${fallback.toString(16).padStart(8, '0')}`;
|
||||
}
|
||||
|
||||
function errorMessage(err: unknown, fallback: string = 'unknown error'): string {
|
||||
if (err instanceof Error && err.message) return err.message;
|
||||
if (typeof err === 'string' && err.trim()) return err.trim();
|
||||
if (typeof err === 'object' && err !== null && 'message' in err) {
|
||||
const message = String((err as { message?: unknown }).message || '').trim();
|
||||
if (message) return message;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function describeMeshChatControlError(raw: string): string {
|
||||
const message = String(raw || '').trim();
|
||||
if (!message) return 'MeshChat could not update the local control plane.';
|
||||
if (
|
||||
message === 'control_plane_request_failed:530' ||
|
||||
message === 'HTTP 530' ||
|
||||
message.includes('control_plane_request_failed:530')
|
||||
) {
|
||||
return 'The local control plane did not complete the lane switch. Check that the backend is running and reachable, then try Mesh again.';
|
||||
}
|
||||
if (
|
||||
message === 'control_plane_request_failed:502' ||
|
||||
message === 'HTTP 502' ||
|
||||
/Backend unavailable/i.test(message)
|
||||
) {
|
||||
return 'The frontend cannot reach the backend right now. Start or restart the backend, then try Mesh again.';
|
||||
}
|
||||
if (message === 'admin_session_required' || /local operator access only/i.test(message)) {
|
||||
return 'This control action needs a local operator session. Open Settings or Node controls once so the app can authorize local changes, then try Mesh again.';
|
||||
}
|
||||
if (message.startsWith('{') || message.startsWith('<')) {
|
||||
return 'MeshChat could not update the local control plane. Check the backend log for the upstream error.';
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
function describeGateCompatConsentRequired(): string {
|
||||
return 'Local gate runtime is unavailable for this room.';
|
||||
}
|
||||
@@ -507,8 +543,13 @@ export function useMeshChatController({
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const detail = await res.text().catch(() => '');
|
||||
throw new Error(detail || `HTTP ${res.status}`);
|
||||
const data = await res.clone().json().catch(() => null) as
|
||||
| { detail?: unknown; message?: unknown; error?: unknown }
|
||||
| null;
|
||||
const detail =
|
||||
String(data?.detail || data?.message || data?.error || '').trim() ||
|
||||
(await res.text().catch(() => '')).trim();
|
||||
throw new Error(describeMeshChatControlError(detail || `HTTP ${res.status}`));
|
||||
}
|
||||
const data = (await res.json()) as MeshMqttSettings;
|
||||
applyMeshMqttSettings(data);
|
||||
@@ -528,7 +569,7 @@ export function useMeshChatController({
|
||||
setMeshMqttStatusText(status);
|
||||
return { ok: true as const, text: status, data };
|
||||
} catch (err) {
|
||||
const text = err instanceof Error ? err.message : 'MQTT settings update failed';
|
||||
const text = describeMeshChatControlError(errorMessage(err, 'MQTT settings update failed'));
|
||||
setMeshMqttStatusText(text);
|
||||
return { ok: false as const, text };
|
||||
} finally {
|
||||
@@ -4222,7 +4263,14 @@ export function useMeshChatController({
|
||||
);
|
||||
|
||||
const disablePrivateNodeForPublicMesh = useCallback(async () => {
|
||||
await setInfonetNodeEnabled(false);
|
||||
try {
|
||||
await setInfonetNodeEnabled(false);
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
'[mesh] private node pre-disable failed before public Mesh activation; MQTT enable will retry lane isolation',
|
||||
err,
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const disableWormholeForPublicMesh = useCallback(async () => {
|
||||
@@ -4287,10 +4335,7 @@ export function useMeshChatController({
|
||||
}
|
||||
return { ok: true as const, text: successText };
|
||||
} catch (err) {
|
||||
const message =
|
||||
typeof err === 'object' && err !== null && 'message' in err
|
||||
? String((err as { message?: string }).message)
|
||||
: 'unknown error';
|
||||
const message = describeMeshChatControlError(errorMessage(err));
|
||||
const errorText =
|
||||
message === 'browser_identity_blocked_secure_mode'
|
||||
? 'Mesh key creation is blocked while Wormhole secure mode is active. Turn Wormhole off first if you want a separate public mesh key.'
|
||||
@@ -4345,10 +4390,7 @@ export function useMeshChatController({
|
||||
setMeshQuickStatus(null);
|
||||
return { ok: true as const, text };
|
||||
} catch (err) {
|
||||
const message =
|
||||
typeof err === 'object' && err !== null && 'message' in err
|
||||
? String((err as { message?: string }).message)
|
||||
: 'unknown error';
|
||||
const message = describeMeshChatControlError(errorMessage(err));
|
||||
const text = `Could not turn MeshChat on: ${message}`;
|
||||
setIdentityWizardStatus({ type: 'err', text });
|
||||
setMeshQuickStatus({ type: 'err', text });
|
||||
|
||||
Reference in New Issue
Block a user