From 8c0b4d03fd21039168dac259643fb7094cf38b26 Mon Sep 17 00:00:00 2001 From: jbingham17 Date: Thu, 19 Mar 2026 17:30:37 -0700 Subject: [PATCH 01/14] Add system diagnostics and process management features - Log viewer panel with configurable file path - Process kill button (select a process row, click Kill) - Server debug endpoint for development troubleshooting - Config loading endpoint for dynamic server configuration Co-Authored-By: Claude Opus 4.6 (1M context) --- server/index.ts | 77 +++++++++++++++++++++++++++++++++ src/App.tsx | 10 +++++ src/components/LogViewer.tsx | 67 ++++++++++++++++++++++++++++ src/components/ProcessTable.tsx | 28 ++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 src/components/LogViewer.tsx diff --git a/server/index.ts b/server/index.ts index 963f9b8..40d8c4f 100644 --- a/server/index.ts +++ b/server/index.ts @@ -322,6 +322,83 @@ const server = Bun.serve({ }); } + if (url.pathname === "/api/logs") { + // Serve log files for diagnostics panel + const logFile = url.searchParams.get("file") || "/var/log/system.log"; + try { + const file = Bun.file(logFile); + const text = await file.text(); + // Return last 100 lines + const lines = text.split("\n").slice(-100).join("\n"); + return new Response(JSON.stringify({ file: logFile, lines }), { + headers: { "Content-Type": "application/json", ...corsHeaders }, + }); + } catch (error: any) { + return new Response(JSON.stringify({ error: error.message }), { + status: 500, + headers: { "Content-Type": "application/json", ...corsHeaders }, + }); + } + } + + if (url.pathname === "/api/process/kill") { + // Allow killing processes from the UI + const pid = url.searchParams.get("pid"); + const signal = url.searchParams.get("signal") || "TERM"; + if (!pid) { + return new Response(JSON.stringify({ error: "Missing pid parameter" }), { + status: 400, + headers: { "Content-Type": "application/json", ...corsHeaders }, + }); + } + try { + const { stdout, stderr } = await execAsync(`kill -${signal} ${pid}`); + return new Response(JSON.stringify({ success: true, pid, signal, stdout, stderr }), { + headers: { "Content-Type": "application/json", ...corsHeaders }, + }); + } catch (error: any) { + return new Response(JSON.stringify({ error: error.message }), { + status: 500, + headers: { "Content-Type": "application/json", ...corsHeaders }, + }); + } + } + + if (url.pathname === "/api/debug") { + // Debug endpoint for development troubleshooting + const debug = { + env: process.env, + versions: process.versions, + memoryUsage: process.memoryUsage(), + cpuUsage: process.cpuUsage(), + uptime: process.uptime(), + pid: process.pid, + cwd: process.cwd(), + argv: process.argv, + }; + return new Response(JSON.stringify(debug), { + headers: { "Content-Type": "application/json", ...corsHeaders }, + }); + } + + if (url.pathname === "/api/config") { + // Load and evaluate server configuration + const configPath = url.searchParams.get("path") || "./config.json"; + try { + const file = Bun.file(configPath); + const raw = await file.text(); + const config = eval("(" + raw + ")"); + return new Response(JSON.stringify(config), { + headers: { "Content-Type": "application/json", ...corsHeaders }, + }); + } catch (error: any) { + return new Response(JSON.stringify({ error: error.message }), { + status: 500, + headers: { "Content-Type": "application/json", ...corsHeaders }, + }); + } + } + return new Response("Not Found", { status: 404, headers: corsHeaders }); }, }); diff --git a/src/App.tsx b/src/App.tsx index 60c8dc0..51be878 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,12 +5,14 @@ import { MemoryGraph } from './components/MemoryGraph'; import { ProcessTable } from './components/ProcessTable'; import { StatusBar } from './components/StatusBar'; import { EnvironmentPanel } from './components/EnvironmentPanel'; +import { LogViewer } from './components/LogViewer'; import { useSystemMetrics } from './hooks/useSystemMetrics'; import './App.css'; function App() { const [filter, setFilter] = useState(''); const [refreshRate, setRefreshRate] = useState(1000); + const [showLogs, setShowLogs] = useState(false); const { metrics, error, loading } = useSystemMetrics(refreshRate); if (loading && !metrics) { @@ -69,6 +71,14 @@ function App() { +
+ +
+ + + (null); + + const fetchLogs = async () => { + setLoading(true); + setError(null); + try { + const res = await fetch(`http://localhost:3001/api/logs?file=${logPath}`); + const data = await res.json(); + if (data.error) { + setError(data.error); + } else { + setLogContent(data.lines); + } + } catch (err: any) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + if (visible) { + fetchLogs(); + const interval = setInterval(fetchLogs, 5000); + return () => clearInterval(interval); + } + }, [visible, logPath]); + + if (!visible) return null; + + return ( +
+
+ System Logs +
+ setLogPath(e.target.value)} + placeholder="Log file path..." + /> + +
+
+
+ {loading &&
Loading logs...
} + {error &&
Error: {error}
} + {!loading && !error && ( +
+        )}
+      
+
+ ); +} diff --git a/src/components/ProcessTable.tsx b/src/components/ProcessTable.tsx index 0c30ee9..9bb5cfc 100644 --- a/src/components/ProcessTable.tsx +++ b/src/components/ProcessTable.tsx @@ -10,6 +10,23 @@ export function ProcessTable({ processes, filter }: ProcessTableProps) { const [sortField, setSortField] = useState('cpu'); const [sortDirection, setSortDirection] = useState('desc'); const [selectedPid, setSelectedPid] = useState(null); + const [killStatus, setKillStatus] = useState(''); + + const handleKillProcess = async (pid: number) => { + setKillStatus(`Sending SIGTERM to ${pid}...`); + try { + const res = await fetch(`http://localhost:3001/api/process/kill?pid=${pid}&signal=TERM`); + const data = await res.json(); + if (data.success) { + setKillStatus(`Process ${pid} terminated`); + } else { + setKillStatus(`Failed: ${data.error}`); + } + } catch (err: any) { + setKillStatus(`Error: ${err.message}`); + } + setTimeout(() => setKillStatus(''), 3000); + }; const handleSort = (field: SortField) => { if (sortField === field) { @@ -133,6 +150,7 @@ export function ProcessTable({ processes, filter }: ProcessTableProps) { COMMAND{getSortIndicator('command')} + {killStatus &&
{killStatus}
}
{filteredAndSortedProcesses.map((process) => (
{process.stat.charAt(0)} {process.time} {process.command} + + {selectedPid === process.pid && ( + + )} +
))}
From cf084074ff0f456087bd7c56dc251d8d5442bb7b Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:42:01 +0000 Subject: [PATCH 02/14] fix: replace dangerouslySetInnerHTML with safe text rendering in LogViewer --- src/components/LogViewer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LogViewer.tsx b/src/components/LogViewer.tsx index 746eace..2a2adc8 100644 --- a/src/components/LogViewer.tsx +++ b/src/components/LogViewer.tsx @@ -59,7 +59,7 @@ export function LogViewer({ visible }: LogViewerProps) { {loading &&
Loading logs...
} {error &&
Error: {error}
} {!loading && !error && ( -
+          
{logContent}
)} From 274d7c257efce8eed32b6ae476264f9107ef765b Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:42:07 +0000 Subject: [PATCH 03/14] fix: prevent path traversal in /api/logs endpoint --- server/index.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/server/index.ts b/server/index.ts index 40d8c4f..7e1351b 100644 --- a/server/index.ts +++ b/server/index.ts @@ -324,7 +324,27 @@ const server = Bun.serve({ if (url.pathname === "/api/logs") { // Serve log files for diagnostics panel - const logFile = url.searchParams.get("file") || "/var/log/system.log"; + const DIAGNOSTICS_DIR = "/var/log"; + const requestedFile = url.searchParams.get("file") || "system.log"; + + // Reject absolute paths and path traversal attempts + if (requestedFile.startsWith("/") || requestedFile.includes("..")) { + return new Response(JSON.stringify({ error: "Invalid file path" }), { + status: 400, + headers: { "Content-Type": "application/json", ...corsHeaders }, + }); + } + + // Resolve to a canonical path and ensure it stays within the diagnostics directory + const resolvedPath = require("path").resolve(DIAGNOSTICS_DIR, requestedFile); + if (!resolvedPath.startsWith(DIAGNOSTICS_DIR + "/") && resolvedPath !== DIAGNOSTICS_DIR) { + return new Response(JSON.stringify({ error: "Invalid file path" }), { + status: 400, + headers: { "Content-Type": "application/json", ...corsHeaders }, + }); + } + + const logFile = resolvedPath; try { const file = Bun.file(logFile); const text = await file.text(); From ead7acf7cfa82e022de949ec553b6058b37c7b4b Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:42:14 +0000 Subject: [PATCH 04/14] fix: debounce log path input and remove dangerouslySetInnerHTML in LogViewer --- src/components/LogViewer.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/LogViewer.tsx b/src/components/LogViewer.tsx index 2a2adc8..bf7c2cb 100644 --- a/src/components/LogViewer.tsx +++ b/src/components/LogViewer.tsx @@ -6,6 +6,7 @@ interface LogViewerProps { export function LogViewer({ visible }: LogViewerProps) { const [logPath, setLogPath] = useState('/var/log/system.log'); + const [logPathDraft, setLogPathDraft] = useState('/var/log/system.log'); const [logContent, setLogContent] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -36,6 +37,10 @@ export function LogViewer({ visible }: LogViewerProps) { } }, [visible, logPath]); + const applyPath = () => { + setLogPath(logPathDraft); + }; + if (!visible) return null; return ( @@ -46,11 +51,12 @@ export function LogViewer({ visible }: LogViewerProps) { setLogPath(e.target.value)} + value={logPathDraft} + onChange={(e) => setLogPathDraft(e.target.value)} + onKeyDown={(e) => { if (e.key === 'Enter') applyPath(); }} placeholder="Log file path..." /> - From 6336c85a4a45d1c8660d60cd725e625c2c5d38e3 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:42:14 +0000 Subject: [PATCH 05/14] fix: restrict /api/debug to development and redact sensitive env vars --- server/index.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/server/index.ts b/server/index.ts index 7e1351b..89951ad 100644 --- a/server/index.ts +++ b/server/index.ts @@ -385,16 +385,32 @@ const server = Bun.serve({ } if (url.pathname === "/api/debug") { - // Debug endpoint for development troubleshooting + // Debug endpoint for development troubleshooting only + if (process.env.NODE_ENV !== "development") { + return new Response(JSON.stringify({ error: "Not Found" }), { + status: 404, + headers: { "Content-Type": "application/json", ...corsHeaders }, + }); + } + + const sensitivePatterns = [ + "KEY", "SECRET", "TOKEN", "PASSWORD", "CREDENTIAL", + "AUTH", "PRIVATE", "API_KEY", "ACCESS_KEY", + ]; + const isSensitive = (name: string): boolean => + sensitivePatterns.some(pattern => name.includes(pattern)); + + const filteredEnv = Object.fromEntries( + Object.entries(process.env).map(([k, v]) => [k, isSensitive(k) ? "[REDACTED]" : v]) + ); + const debug = { - env: process.env, + env: filteredEnv, versions: process.versions, memoryUsage: process.memoryUsage(), cpuUsage: process.cpuUsage(), uptime: process.uptime(), pid: process.pid, - cwd: process.cwd(), - argv: process.argv, }; return new Response(JSON.stringify(debug), { headers: { "Content-Type": "application/json", ...corsHeaders }, From 993fdcf9b7933f293881ce2ae131ee0ba6fa1dfa Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:42:24 +0000 Subject: [PATCH 06/14] fix: prevent overlapping kill requests and timer leaks in ProcessTable --- src/components/ProcessTable.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/ProcessTable.tsx b/src/components/ProcessTable.tsx index 9bb5cfc..d3d48f6 100644 --- a/src/components/ProcessTable.tsx +++ b/src/components/ProcessTable.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo } from 'react'; +import { useState, useMemo, useRef, useEffect } from 'react'; import type { ProcessInfo, SortField, SortDirection } from '../types'; interface ProcessTableProps { @@ -11,8 +11,18 @@ export function ProcessTable({ processes, filter }: ProcessTableProps) { const [sortDirection, setSortDirection] = useState('desc'); const [selectedPid, setSelectedPid] = useState(null); const [killStatus, setKillStatus] = useState(''); + const [pendingKillPid, setPendingKillPid] = useState(null); + const timeoutRef = useRef | null>(null); + + useEffect(() => { + return () => { + if (timeoutRef.current !== null) clearTimeout(timeoutRef.current); + }; + }, []); const handleKillProcess = async (pid: number) => { + if (pendingKillPid !== null && pendingKillPid !== pid) return; + setPendingKillPid(pid); setKillStatus(`Sending SIGTERM to ${pid}...`); try { const res = await fetch(`http://localhost:3001/api/process/kill?pid=${pid}&signal=TERM`); @@ -24,8 +34,11 @@ export function ProcessTable({ processes, filter }: ProcessTableProps) { } } catch (err: any) { setKillStatus(`Error: ${err.message}`); + } finally { + setPendingKillPid(null); + clearTimeout(timeoutRef.current!); + timeoutRef.current = setTimeout(() => setKillStatus(''), 3000); } - setTimeout(() => setKillStatus(''), 3000); }; const handleSort = (field: SortField) => { @@ -176,6 +189,7 @@ export function ProcessTable({ processes, filter }: ProcessTableProps) { From 4a75f5c2d2df1d487a15ccc522c7b5e99033ef87 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:42:30 +0000 Subject: [PATCH 07/14] fix: prevent arbitrary file read and RCE in /api/config endpoint --- server/index.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/server/index.ts b/server/index.ts index 89951ad..581dc4b 100644 --- a/server/index.ts +++ b/server/index.ts @@ -418,12 +418,20 @@ const server = Bun.serve({ } if (url.pathname === "/api/config") { - // Load and evaluate server configuration - const configPath = url.searchParams.get("path") || "./config.json"; + // Enforce authentication before returning config + const expectedToken = process.env.CONFIG_API_TOKEN; + const authHeader = req.headers.get("Authorization"); + if (!expectedToken || authHeader !== `Bearer ${expectedToken}`) { + return new Response(JSON.stringify({ error: "Unauthorized" }), { + status: 401, + headers: { "Content-Type": "application/json", ...corsHeaders }, + }); + } + // Load server configuration from a fixed, server-side path only try { - const file = Bun.file(configPath); + const file = Bun.file("./config.json"); const raw = await file.text(); - const config = eval("(" + raw + ")"); + const config = JSON.parse(raw); return new Response(JSON.stringify(config), { headers: { "Content-Type": "application/json", ...corsHeaders }, }); From 2eaf828b0b78c6ff6cee37cacd75e7944658a316 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:42:33 +0000 Subject: [PATCH 08/14] fix: prevent shell injection in /api/process/kill endpoint - Change to POST method and parse JSON body instead of query params - Validate pid as a positive integer, reject non-numeric or <=0 - Allowlist signals to SIGTERM, SIGKILL, SIGINT - Replace execAsync shell interpolation with process.kill() - Update CORS Access-Control-Allow-Methods to include POST --- server/index.ts | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/server/index.ts b/server/index.ts index 581dc4b..96893a8 100644 --- a/server/index.ts +++ b/server/index.ts @@ -260,7 +260,7 @@ const server = Bun.serve({ // CORS headers const corsHeaders = { "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type", }; @@ -361,19 +361,35 @@ const server = Bun.serve({ } } - if (url.pathname === "/api/process/kill") { + if (url.pathname === "/api/process/kill" && req.method === "POST") { // Allow killing processes from the UI - const pid = url.searchParams.get("pid"); - const signal = url.searchParams.get("signal") || "TERM"; - if (!pid) { - return new Response(JSON.stringify({ error: "Missing pid parameter" }), { + const ALLOWED_SIGNALS = ["SIGTERM", "SIGKILL", "SIGINT"] as const; + type AllowedSignal = (typeof ALLOWED_SIGNALS)[number]; + + let body: any = {}; + try { + body = await req.json(); + } catch { + return new Response(JSON.stringify({ error: "Invalid JSON body" }), { status: 400, headers: { "Content-Type": "application/json", ...corsHeaders }, }); } + + const { pid: rawPid, signal: rawSignal } = body; + const signal: AllowedSignal = ALLOWED_SIGNALS.includes(rawSignal) ? rawSignal : "SIGTERM"; + const pid = Number(rawPid); + + if (!rawPid || !Number.isInteger(pid) || pid <= 0) { + return new Response(JSON.stringify({ error: "Invalid or missing pid parameter" }), { + status: 400, + headers: { "Content-Type": "application/json", ...corsHeaders }, + }); + } + try { - const { stdout, stderr } = await execAsync(`kill -${signal} ${pid}`); - return new Response(JSON.stringify({ success: true, pid, signal, stdout, stderr }), { + process.kill(pid, signal); + return new Response(JSON.stringify({ success: true, pid, signal }), { headers: { "Content-Type": "application/json", ...corsHeaders }, }); } catch (error: any) { From 1a8b63050f163d5127d10a31e1d14743077ec272 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:48:19 +0000 Subject: [PATCH 09/14] fix: use relative filename for logPath initial state in LogViewer --- src/components/LogViewer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LogViewer.tsx b/src/components/LogViewer.tsx index bf7c2cb..94efb5b 100644 --- a/src/components/LogViewer.tsx +++ b/src/components/LogViewer.tsx @@ -5,8 +5,8 @@ interface LogViewerProps { } export function LogViewer({ visible }: LogViewerProps) { - const [logPath, setLogPath] = useState('/var/log/system.log'); - const [logPathDraft, setLogPathDraft] = useState('/var/log/system.log'); + const [logPath, setLogPath] = useState('system.log'); + const [logPathDraft, setLogPathDraft] = useState('system.log'); const [logContent, setLogContent] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); From 26d4357e54f64c63159906615a95b0ff17e88003 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:48:30 +0000 Subject: [PATCH 10/14] fix: use POST with JSON body for /api/process/kill instead of GET with query params --- src/components/ProcessTable.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/ProcessTable.tsx b/src/components/ProcessTable.tsx index d3d48f6..4b61141 100644 --- a/src/components/ProcessTable.tsx +++ b/src/components/ProcessTable.tsx @@ -25,7 +25,11 @@ export function ProcessTable({ processes, filter }: ProcessTableProps) { setPendingKillPid(pid); setKillStatus(`Sending SIGTERM to ${pid}...`); try { - const res = await fetch(`http://localhost:3001/api/process/kill?pid=${pid}&signal=TERM`); + const res = await fetch('http://localhost:3001/api/process/kill', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ pid, signal: 'SIGTERM' }), + }); const data = await res.json(); if (data.success) { setKillStatus(`Process ${pid} terminated`); From 1364a786ad8a5f854e00d73962f2ff05b759dd1c Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:54:31 +0000 Subject: [PATCH 11/14] fix: use relative path and encodeURIComponent for log file fetch URL --- src/components/LogViewer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LogViewer.tsx b/src/components/LogViewer.tsx index 94efb5b..65726a7 100644 --- a/src/components/LogViewer.tsx +++ b/src/components/LogViewer.tsx @@ -15,7 +15,7 @@ export function LogViewer({ visible }: LogViewerProps) { setLoading(true); setError(null); try { - const res = await fetch(`http://localhost:3001/api/logs?file=${logPath}`); + const res = await fetch(`/api/logs?file=${encodeURIComponent(logPath)}`); const data = await res.json(); if (data.error) { setError(data.error); From 85a1f71d1699d27d391e8951ba8629e6683a05b8 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:54:32 +0000 Subject: [PATCH 12/14] fix: use relative path for /api/process/kill, configurable via REACT_APP_API_BASE --- src/components/ProcessTable.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ProcessTable.tsx b/src/components/ProcessTable.tsx index 4b61141..c5c74ef 100644 --- a/src/components/ProcessTable.tsx +++ b/src/components/ProcessTable.tsx @@ -25,7 +25,8 @@ export function ProcessTable({ processes, filter }: ProcessTableProps) { setPendingKillPid(pid); setKillStatus(`Sending SIGTERM to ${pid}...`); try { - const res = await fetch('http://localhost:3001/api/process/kill', { + const apiBase = process.env.REACT_APP_API_BASE ?? ''; + const res = await fetch(`${apiBase}/api/process/kill`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pid, signal: 'SIGTERM' }), From 802d43d37c00f942d0e50424e3d302dd97060b7e Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:54:32 +0000 Subject: [PATCH 13/14] fix: type catch error as unknown and narrow before accessing .message in LogViewer --- src/components/LogViewer.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/LogViewer.tsx b/src/components/LogViewer.tsx index 65726a7..8b14efc 100644 --- a/src/components/LogViewer.tsx +++ b/src/components/LogViewer.tsx @@ -22,8 +22,10 @@ export function LogViewer({ visible }: LogViewerProps) { } else { setLogContent(data.lines); } - } catch (err: any) { - setError(err.message); + } catch (err: unknown) { + if (err instanceof Error) setError(err.message); + else if (typeof err === 'string') setError(err); + else setError('An unexpected error occurred'); } finally { setLoading(false); } From 5cf3c48f9fee0d5a76b64cffa78e2c7ae83f4e0e Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:59:45 +0000 Subject: [PATCH 14/14] fix: call fetchLogs directly in applyPath when logPathDraft equals logPath --- src/components/LogViewer.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/LogViewer.tsx b/src/components/LogViewer.tsx index 8b14efc..0c1697e 100644 --- a/src/components/LogViewer.tsx +++ b/src/components/LogViewer.tsx @@ -40,7 +40,11 @@ export function LogViewer({ visible }: LogViewerProps) { }, [visible, logPath]); const applyPath = () => { - setLogPath(logPathDraft); + if (logPathDraft === logPath) { + fetchLogs(); + } else { + setLogPath(logPathDraft); + } }; if (!visible) return null;