From 64ab6ccf27c5eb293ec457f30bb74c6621f9d93e Mon Sep 17 00:00:00 2001 From: 2GT-Rich <107219997+2GT-Rich@users.noreply.github.com> Date: Sat, 16 May 2026 16:28:40 -0700 Subject: [PATCH] fix: show real capture error + step-by-step backend logging - Frontend: blob error responses (from responseType:'blob' calls) are now decoded back to text/JSON so the actual backend error message surfaces in the UI instead of generic "Request failed with status code 500" - Backend: step-by-step console.log through capture flow so the exact failure point is visible in server logs Co-Authored-By: Claude Sonnet 4.6 --- backend/src/routes/devices.ts | 5 +++++ .../src/components/device-detail/ToolsTab.tsx | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/backend/src/routes/devices.ts b/backend/src/routes/devices.ts index 59e0c91..1e6a31c 100644 --- a/backend/src/routes/devices.ts +++ b/backend/src/routes/devices.ts @@ -1793,19 +1793,24 @@ router.post('/:id/tools/capture', requireWrite, async (req: Request, res: Respon const client = makeToolClient(device); try { + console.log(`[capture] connecting to device ${device.id} (${device.ip_address})`); await client.connect(); + console.log(`[capture] connected; configuring sniffer`); // Stop any previous capture before reconfiguring await client.execute('/tool/sniffer/stop').catch(() => {}); const setParams: Record = { 'file-name': fileName, 'file-limit': '10240' }; if (iface) setParams['interface'] = iface; if (filter_ip) setParams['filter-ip-address'] = filter_ip; await client.execute('/tool/sniffer/set', setParams); + console.log(`[capture] sniffer configured; starting ${captureSec}s capture`); await client.execute('/tool/sniffer/start'); await new Promise((r) => setTimeout(r, captureSec * 1000)); await client.execute('/tool/sniffer/stop').catch(() => {}); client.disconnect(); + console.log(`[capture] sniffer stopped; waiting for file flush`); // Allow RouterOS a moment to flush the PCAP file before SFTP download await new Promise((r) => setTimeout(r, 2000)); + console.log(`[capture] opening SFTP to ${device.ip_address}:${device.ssh_port ?? 22} path=${remotePath}`); // Download PCAP via SFTP then delete remote file const pcapBuffer = await new Promise((resolve, reject) => { diff --git a/frontend/src/components/device-detail/ToolsTab.tsx b/frontend/src/components/device-detail/ToolsTab.tsx index 851c6fb..e30c139 100644 --- a/frontend/src/components/device-detail/ToolsTab.tsx +++ b/frontend/src/components/device-detail/ToolsTab.tsx @@ -407,8 +407,20 @@ function PacketCaptureTool({ deviceId, interfaces }: { deviceId: number; interfa setFileName(name); setReady(true); } catch (e: unknown) { - const msg = (e as { response?: { data?: { error?: string } } })?.response?.data?.error; - setError(msg || (e as Error).message || 'Capture failed'); + // responseType:'blob' means error bodies arrive as Blobs — parse them back to text + const blobData = (e as { response?: { data?: unknown } })?.response?.data; + if (blobData instanceof Blob) { + try { + const text = await blobData.text(); + const json = JSON.parse(text) as { error?: string }; + setError(json.error || text || 'Capture failed'); + } catch { + setError('Capture failed'); + } + } else { + const msg = (e as { response?: { data?: { error?: string } } })?.response?.data?.error; + setError(msg || (e as Error).message || 'Capture failed'); + } } finally { setLoading(false); }