import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { X, Play, Square, Trash2, RefreshCw, ArrowDown, ArrowUp, Search } from 'lucide-react'; import { api, type MQTTLogEntry } from '../api/client'; import { Button } from './Button'; import { useState, useEffect, useRef, useMemo } from 'react'; interface MQTTDebugModalProps { printerId: number; printerName: string; onClose: () => void; } export function MQTTDebugModal({ printerId, printerName, onClose }: MQTTDebugModalProps) { const { t } = useTranslation(); const queryClient = useQueryClient(); const [autoScroll, setAutoScroll] = useState(true); const [expandedLogs, setExpandedLogs] = useState>(new Set()); const [searchQuery, setSearchQuery] = useState(''); const [directionFilter, setDirectionFilter] = useState<'all' | 'in' | 'out'>('all'); const logContainerRef = useRef(null); const { data, isLoading, refetch } = useQuery({ queryKey: ['mqtt-logs', printerId], queryFn: () => api.getMQTTLogs(printerId), refetchInterval: 1000, // Poll every second when logging is enabled }); const enableMutation = useMutation({ mutationFn: () => api.enableMQTTLogging(printerId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['mqtt-logs', printerId] }); }, }); const disableMutation = useMutation({ mutationFn: () => api.disableMQTTLogging(printerId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['mqtt-logs', printerId] }); }, }); const clearMutation = useMutation({ mutationFn: () => api.clearMQTTLogs(printerId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['mqtt-logs', printerId] }); }, }); // Close on Escape key useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [onClose]); // Auto-scroll to bottom when new logs arrive useEffect(() => { if (autoScroll && logContainerRef.current) { logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight; } }, [data?.logs, autoScroll]); const toggleExpand = (index: number) => { setExpandedLogs((prev) => { const newSet = new Set(prev); if (newSet.has(index)) { newSet.delete(index); } else { newSet.add(index); } return newSet; }); }; const formatTimestamp = (timestamp: string) => { const date = new Date(timestamp); return date.toLocaleTimeString('en-US', { hour12: false, fractionalSecondDigits: 3 }); }; const formatPayload = (payload: unknown, expanded: boolean): string => { if (payload === undefined || payload === null) { return ''; } // If payload is already a string, parse it first to format nicely const obj = typeof payload === 'string' ? JSON.parse(payload) : payload; const json = JSON.stringify(obj, null, expanded ? 2 : 0); if (!expanded && json.length > 100) { return json.substring(0, 100) + '...'; } return json; }; const loggingEnabled = data?.logging_enabled ?? false; const logs = useMemo(() => data?.logs ?? [], [data?.logs]); // Filter logs based on search query and direction filter const filteredLogs = useMemo(() => { return logs.filter((log) => { // Direction filter if (directionFilter !== 'all' && log.direction !== directionFilter) { return false; } // Search filter if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); const topicMatch = log.topic.toLowerCase().includes(query); const payloadStr = JSON.stringify(log.payload).toLowerCase(); const payloadMatch = payloadStr.includes(query); return topicMatch || payloadMatch; } return true; }); }, [logs, searchQuery, directionFilter]); return (
{/* Header */}

{t('mqttDebug.title')}

{printerName}

{/* Controls */}
{loggingEnabled ? ( ) : ( )}
{filteredLogs.length}/{logs.length}
{/* Search and Filter Row */}
setSearchQuery(e.target.value)} className="w-full pl-8 pr-3 py-1.5 text-sm bg-bambu-dark border border-bambu-dark-tertiary rounded text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" /> {searchQuery && ( )}
{/* Log Content */}
{logs.length === 0 ? (

{t('mqttDebug.noMessages')}

{!loggingEnabled && (

{t('mqttDebug.startLoggingHint')}

)}
) : filteredLogs.length === 0 ? (

{t('mqttDebug.noMessagesMatch')}

{t('mqttDebug.adjustFilterHint')}

) : (
{filteredLogs.map((log: MQTTLogEntry, index: number) => { const isExpanded = expandedLogs.has(index); const isIncoming = log.direction === 'in'; return (
toggleExpand(index)} >
{formatTimestamp(log.timestamp)} {isIncoming ? ( ) : ( )} {log.topic}
{isExpanded ? (
                        {formatPayload(log.payload, true)}
                      
) : (
                        {formatPayload(log.payload, false)}
                      
)}
); })}
)}
{/* Footer */}
{loggingEnabled ? ( {t('mqttDebug.loggingActive')} ) : ( {t('mqttDebug.loggingStopped')} )}
); }