diff --git a/server/server.go b/server/server.go index 0a97719..a653371 100644 --- a/server/server.go +++ b/server/server.go @@ -178,7 +178,7 @@ func (s *Server) handleStream(w http.ResponseWriter, r *http.Request) { // Create a marshaler with pretty printing enabled marshaler := protojson.MarshalOptions{ EmitUnpopulated: true, - Indent: " ", + Multiline: false, UseProtoNames: false, // Use camelCase names } diff --git a/web/src/components/InfoMessage.tsx b/web/src/components/InfoMessage.tsx new file mode 100644 index 0000000..de7d303 --- /dev/null +++ b/web/src/components/InfoMessage.tsx @@ -0,0 +1,41 @@ +import React from 'react'; + +interface InfoMessageProps { + message: string; + type?: 'info' | 'warning' | 'error'; +} + +export const InfoMessage: React.FC = ({ + message, + type = 'info' +}) => { + const getBgColor = () => { + switch (type) { + case 'warning': + return 'bg-yellow-50 border-yellow-200'; + case 'error': + return 'bg-red-50 border-red-200'; + case 'info': + default: + return 'bg-blue-50 border-blue-200'; + } + }; + + const getTextColor = () => { + switch (type) { + case 'warning': + return 'text-yellow-800'; + case 'error': + return 'text-red-800'; + case 'info': + default: + return 'text-blue-800'; + } + }; + + return ( +
+

{message}

+
+ ); +}; \ No newline at end of file diff --git a/web/src/components/index.ts b/web/src/components/index.ts index 1120812..7ca036f 100644 --- a/web/src/components/index.ts +++ b/web/src/components/index.ts @@ -2,3 +2,4 @@ export * from './PacketList'; export * from './MessageDisplay'; export * from './PacketDetails'; export * from './Filter'; +export * from './InfoMessage'; diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index 5f30100..8cb5d6b 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -1,7 +1,7 @@ /** * API client functions for interacting with the Meshstream server */ -import { API_ENDPOINTS } from './config'; +import { API_ENDPOINTS } from "./config"; export interface ApiResponse { data?: T; @@ -28,16 +28,21 @@ export async function fetchRecentPackets(): Promise> { * Type definitions for SSE events */ export interface InfoEvent { - type: 'info'; + type: "info"; data: string; } export interface MessageEvent { - type: 'message'; + type: "message"; data: any; // Will be the parsed JSON message } -export type StreamEvent = InfoEvent | MessageEvent; +export interface BadDataEvent { + type: "bad_data"; + data: string; // Raw data that failed to parse +} + +export type StreamEvent = InfoEvent | MessageEvent | BadDataEvent; export type StreamEventHandler = (event: StreamEvent) => void; @@ -45,40 +50,35 @@ export type StreamEventHandler = (event: StreamEvent) => void; * Establish a Server-Sent Events connection to receive real-time packets */ export function streamPackets( - onEvent: StreamEventHandler, + onEvent: StreamEventHandler, onError?: (error: Event) => void ): () => void { const evtSource = new EventSource(API_ENDPOINTS.STREAM); - - // Handle general messages (fallback) - evtSource.onmessage = (event) => { - handleEventData('message', event.data, onEvent); - }; - + // Handle info events specifically - evtSource.addEventListener('info', (event) => { + evtSource.addEventListener("info", (event) => { // Info events are just strings onEvent({ - type: 'info', - data: event.data + type: "info", + data: event.data, }); }); - + // Handle message events specifically - evtSource.addEventListener('message', (event) => { - handleEventData('message', event.data, onEvent); + evtSource.addEventListener("message", (event) => { + handleEventData(event.data, onEvent); }); - + // Handle errors if (onError) { evtSource.onerror = onError; } else { evtSource.onerror = () => { - console.error('EventSource failed'); + console.error("EventSource failed"); evtSource.close(); }; } - + // Return cleanup function return () => evtSource.close(); } @@ -86,36 +86,19 @@ export function streamPackets( /** * Helper to handle event data based on type */ -function handleEventData( - type: 'info' | 'message', - data: string, - callback: StreamEventHandler -): void { +function handleEventData(data: string, callback: StreamEventHandler): void { try { - if (type === 'info') { - // Info events are plain text - callback({ - type: 'info', - data - }); - } else { - // Message events are JSON - try { - const parsedData = JSON.parse(data); - callback({ - type: 'message', - data: parsedData - }); - } catch (error) { - // If JSON parsing fails, treat it as a plain text message - console.warn('Failed to parse message as JSON:', error); - callback({ - type: 'info', - data - }); - } - } + const parsedData = JSON.parse(data); + + callback({ + type: "message", + data: parsedData, + }); } catch (error) { - console.error('Error handling event data:', error); + console.warn("Failed to parse message as JSON:", error, "Raw data:", data); + callback({ + type: "bad_data", + data, + }); } } diff --git a/web/src/routes/packets.tsx b/web/src/routes/packets.tsx index 6233070..a72c36a 100644 --- a/web/src/routes/packets.tsx +++ b/web/src/routes/packets.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { useAppDispatch } from '../hooks'; import { PacketList } from '../components/PacketList'; +import { InfoMessage } from '../components/InfoMessage'; import { addPacket } from '../store/slices/packetSlice'; import { streamPackets, StreamEvent } from '../lib/api'; @@ -37,10 +38,10 @@ export function PacketsRoute() {

Mesh Network Packets

{/* Connection status indicator */} -
- Status: - {connectionStatus} -
+

This page displays real-time packets from the Meshtastic mesh network.