Skip to content

feat(mqtt-bridge): mirror dashboard for MQTT Bridge sources#3143

Merged
Yeraze merged 1 commit into
mainfrom
feat/mqtt-bridge-dashboard
May 22, 2026
Merged

feat(mqtt-bridge): mirror dashboard for MQTT Bridge sources#3143
Yeraze merged 1 commit into
mainfrom
feat/mqtt-bridge-dashboard

Conversation

@Yeraze

@Yeraze Yeraze commented May 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds an Open button to mqtt_bridge cards on the source list and routes to a read-only mirror of the Meshtastic dashboard. The mirror suppresses every surface that would call out to the mesh — bridges only ingest, they don't transmit.
  • Fixes the 500 errors from /api/poll and /api/connection when the active manager is an MqttBridgeManager — previously those endpoints called Meshtastic-specific methods on the manager unconditionally, leaving bridge dashboards blank.

UI changes (gated by mqttReadOnly / isMqttBridge)

  • Sidebar: hides Device Configuration, Automation, Remote Administration.
  • Channels tab: hides send composer and the Show MQTT messages toggle (every packet has viaMqtt:true, so the filter would always blank the list). Per-channel permission filter is relaxed so slots outside 0-7 surface in the picker — bridges routinely see arbitrary slot indices from upstream nodes with custom configs.
  • Messages tab: hides the DM message log and the send composer (telemetry block stays visible), hides the four top action buttons (Traceroute, Exchange Node Info, Exchange Position, Request Neighbor Info) plus the matching dropdown items (Traceroute, Exchange Position, Exchange Node Info, Request Telemetry, Scan for Admin). Traceroute History stays because it's read-only.
  • AppHeader: hides the node-info slot so the env-default Meshtastic IP doesn't leak in as a fallback when the bridge has no local device.

Server changes

  • MqttBridgeManager now exposes the Meshtastic-shaped methods the consolidated /api/poll + /api/connection routes call through resolveSourceManager: getConnectionStatus, getAllNodesAsync, getDeviceConfig (null), getDeviceNodeNums (empty), getSecurityKeys (nulls).
  • mapDbNodeToDeviceInfo + a new loadAllNodesAsDeviceInfo(sourceId) are extracted into src/server/utils/dbNodeMapper.ts so both managers share the projection.

Plumbing

  • SourceContext now carries sourceType so App.tsx can derive isMqttBridge and thread mqttReadOnly through Sidebar / ChannelsTab / MessagesTab / AppHeader.
  • Tab-permission gates also check !isMqttBridge so URL-hash navigation to #configuration / #admin / #automation redirects out.

Test plan

  • Open a Meshtastic TCP source — sidebar still shows Device Config / Automation / Remote Admin; Channels/Messages send composers visible; DM action buttons present.
  • Open the new MQTT bridge dashboard — sidebar omits Device Config / Automation / Remote Admin; node-info slot in header is empty.
  • Channels tab: picker shows every channel the bridge has heard (including slots 8/31), each channel renders its messages.
  • DM tab: pick a node — top action buttons (Traceroute / Exchange Node Info / Exchange Position / Request Neighbor Info) are gone; Show on Map still works. Open the Actions dropdown — Traceroute / Exchange / Request Telemetry / Scan for Admin are gone; History / Favorite / Lock / Ignore / Override Position / Purge Data remain. DM message log is hidden, telemetry block visible.
  • curl /api/connection?sourceId=<bridge> and /api/poll?sourceId=<bridge> both return 200.
  • Vitest suites still pass (DashboardSidebar, MqttBridgeManager, App, etc.).

🤖 Generated with Claude Code

Adds an "Open" button to mqtt_bridge cards on the source list and routes
to a read-only mirror of the Meshtastic dashboard. Bridges ingest packets
from upstream but have no transmit path, so every surface that would call
out to the mesh is suppressed:

- Sidebar: hide Device Configuration, Automation, Remote Administration
- Channels: hide send composer + the `Show MQTT messages` toggle (every
  packet a bridge ingests has viaMqtt=true, so the filter would always
  blank the list — also relax the per-channel permission filter so slots
  outside 0-7 surface, since bridges see arbitrary slot indices)
- Messages: hide DM message log + send composer (telemetry block remains),
  hide the four top action buttons (Traceroute / Exchange Node Info /
  Exchange Position / Request Neighbor Info) and the matching items in the
  DM Actions dropdown (Traceroute, Exchange Position, Exchange Node Info,
  Request Telemetry, Scan for Admin). Traceroute History remains since
  it's a read-only view.
- AppHeader: hide the node-info slot so the env-default Meshtastic IP
  doesn't leak in as a fallback when the bridge has no local device.

Server side, MqttBridgeManager now exposes the Meshtastic-shaped methods
the consolidated /api/poll + /api/connection routes call through
resolveSourceManager (getConnectionStatus, getAllNodesAsync,
getDeviceConfig, getDeviceNodeNums, getSecurityKeys). Without these the
endpoints 500'd with `TypeError: ... is not a function` and the whole
dashboard stayed blank. mapDbNodeToDeviceInfo + loadAllNodesAsDeviceInfo
are extracted to src/server/utils/dbNodeMapper.ts so both managers share
the projection.

SourceContext now carries sourceType so App.tsx can derive isMqttBridge
and thread mqttReadOnly through Sidebar / ChannelsTab / MessagesTab /
AppHeader. Tab-permission gates also check !isMqttBridge so URL-hash
navigation to #configuration / #admin / #automation redirects out.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 22, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


Code Review Complete ✅

I've analyzed this MQTT Bridge dashboard PR and found it to be well-implemented with good adherence to the project's architecture. Here's my comprehensive review:

Strengths

Architecture & Design

  • Excellent multi-source adherence: The implementation properly follows CLAUDE.md guidelines by threading sourceType through SourceContext and implementing manager-agnostic APIs
  • Clean separation of concerns: UI gating logic is consistently applied using the isMqttBridge/mqttReadOnly pattern across components
  • Smart code reuse: Extracting dbNodeMapper.ts utility eliminates duplication between MeshtasticManager and MqttBridgeManager

API Design

  • Manager compatibility: The new Meshtastic-shaped methods (getConnectionStatus, getAllNodesAsync, etc.) enable clean integration with existing /api/poll and /api/connection endpoints
  • Defensive defaults: Returning sensible nulls/empties for device-specific APIs (no local device means no config, keys, or device NodeDB)

UI Implementation

  • Comprehensive gating: All transmit surfaces properly hidden (send composers, action buttons, admin tabs)
  • Context-aware filtering: Channel permission relaxation for arbitrary slot indices makes sense for bridge scenarios
  • Consistent UX: Read-only restrictions are applied systematically across Sidebar, ChannelsTab, MessagesTab, and AppHeader

Issues & Recommendations

Security & Edge Cases

  1. Missing input validation in MqttBridgeManager:

    // src/server/mqttBridgeManager.ts:271
    async getAllNodesAsync(sourceId?: string): Promise<DeviceInfo[]> {
      return loadAllNodesAsDeviceInfo(sourceId); // Should validate sourceId matches this.sourceId
    }

    Risk: Caller could potentially access nodes from other sources
    Fix: Add validation: if (sourceId && sourceId !== this.sourceId) throw new Error('Invalid source access')

  2. Missing error handling in dbNodeMapper.ts:130:

    export async function loadAllNodesAsDeviceInfo(sourceId?: string): Promise<DeviceInfo[]> {
      // No try/catch around database operations

    Fix: Wrap in try/catch and provide meaningful error messages

Performance Concerns

  1. Potential N+1 query issue in loadAllNodesAsDeviceInfo():

    • Calls getLatestTelemetryValueForAllNodes() then getAllNodes()
    • Could be optimized with a single JOIN query for better performance at scale
  2. Missing test coverage for the new manager methods:

    • None of the new Meshtastic-shaped methods are covered by existing tests
    • Should add tests for getConnectionStatus(), getAllNodesAsync(), etc.

Code Quality

  1. Hardcoded string literal:

    // src/App.tsx:124
    const isMqttBridge = sourceType === 'mqtt_bridge';

    Recommendation: Extract to a constant in src/server/constants/ for consistency

  2. Type safety: Some any usage in mapDbNodeToDeviceInfo could be tightened with proper interfaces

Minor Suggestions

  • Consider adding JSDoc comments to the new manager methods for API documentation
  • The large conditional chain in mapDbNodeToDeviceInfo could benefit from a helper function or object mapping approach
  • Consider extracting the mqttReadOnly prop threading pattern into a custom hook for cleaner component APIs

Test Plan Validation

The test plan looks comprehensive and covers the key scenarios. I'd add:

  • Verify that URL navigation to gated tabs (#configuration, #admin, #automation) properly redirects when isMqttBridge=true
  • Test edge cases like switching between MQTT bridge and regular sources
  • Confirm that MQTT bridge sources don't leak data across source boundaries

Overall Assessment

This is a solid implementation that successfully extends MeshMonitor's multi-source architecture to support read-only MQTT bridge dashboards. The code follows project conventions well and the UI gating is comprehensive. With the security validation fix and test coverage additions, this will be ready for production.

Recommendation: Approve with minor fixes


@Yeraze Yeraze merged commit 1ea2fbb into main May 22, 2026
19 checks passed
@Yeraze Yeraze deleted the feat/mqtt-bridge-dashboard branch May 22, 2026 21:49
Yeraze added a commit that referenced this pull request May 22, 2026
…ish (#3145)

Version bump and CHANGELOG entry for 4.6.5. Highlights:

- #3143 MQTT Bridge mirror dashboard (read-only Open button on bridge
  cards; suppresses every TX surface; MqttBridgeManager now exposes the
  Meshtastic-shaped methods the consolidated /api/poll + /api/connection
  endpoints expect).
- #3142 MeshCore Telemetry dashboard tab via source-agnostic Dashboard.
- #3141 MeshCore: preserve LPP channel byte in remote telemetry rows.
- #3140 DM: skip PKI flag when keyMismatchDetected.
- #3138 docs reorganization.
- #3137 unified: merge nodes across sources so labels show in the
  unified Nodes view.
- #3136 mqtt: allow standalone mqtt_bridge as client-proxy target.

CLAUDE.md version line updated to 4.6.5.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant