This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 0bda8d2c504a87effe316d544f653dd96e99c4e2 Author: Claus Ibsen <[email protected]> AuthorDate: Fri Jul 10 16:17:24 2026 +0200 TUI: Fix various bugs in trace reading, MCP file handling, CVE caching, route actions, and thread safety - DataRefreshService: Remove incorrect readLine() skip that discarded complete trace lines on incremental reads - CamelMonitor: Skip writing .mcp.json when the file already exists to avoid overwriting user config - OsvClient: Only cache empty CVE results on successful API responses, leaving failed lookups for retry - RoutesTab: Use shared displayedRoutes() helper so Top-mode actions target the highlighted route - AiPanel/SendMessagePopup: Use CopyOnWriteArrayList for cross-thread conversation and history lists Co-Authored-By: Claude Opus 4.6 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../camel/dsl/jbang/core/commands/tui/AiPanel.java | 5 +- .../dsl/jbang/core/commands/tui/CamelMonitor.java | 4 ++ .../core/commands/tui/DataRefreshService.java | 7 +- .../dsl/jbang/core/commands/tui/OsvClient.java | 16 +++-- .../dsl/jbang/core/commands/tui/RoutesTab.java | 83 ++++++++-------------- .../jbang/core/commands/tui/SendMessagePopup.java | 3 +- 6 files changed, 50 insertions(+), 68 deletions(-) diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java index 6f24b8b1df65..e1a33c508984 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; import dev.tamboui.layout.Constraint; @@ -83,7 +84,7 @@ class AiPanel { private int cursorPos; // Conversation display - private final List<ConversationEntry> conversation = new ArrayList<>(); + private final List<ConversationEntry> conversation = new CopyOnWriteArrayList<>(); private int scrollOffset; // LLM state @@ -103,7 +104,7 @@ class AiPanel { private Rect lastArea; // AI usage stats - private final List<AiUsageEntry> usageHistory = new ArrayList<>(); + private final List<AiUsageEntry> usageHistory = new CopyOnWriteArrayList<>(); private final TableState statsTableState = new TableState(); private boolean statsView; private int statsScrollOffset; diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java index 8ef6eeda1ed5..7f357bd7e3ef 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java @@ -2105,6 +2105,10 @@ public class CamelMonitor extends CamelCommand { private static Path writeMcpJson(int port) { Path path = Path.of(".mcp.json"); + if (Files.exists(path)) { + // do not overwrite an existing .mcp.json (may belong to Claude Code, Cursor, etc.) + return null; + } try { String json = """ { diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java index 729336259484..bc02ece17db6 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java @@ -510,14 +510,9 @@ class DataRefreshService { } raf.seek(lastPos); - // If we're resuming mid-file, skip any partial line - if (lastPos > 0) { - raf.readLine(); - } // Read remaining bytes - long startPos = raf.getFilePointer(); - byte[] remaining = new byte[(int) (length - startPos)]; + byte[] remaining = new byte[(int) (length - lastPos)]; raf.readFully(remaining); String content = new String(remaining, StandardCharsets.UTF_8); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OsvClient.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OsvClient.java index 0d346b94f33b..5c9bc0fb1ba5 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OsvClient.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OsvClient.java @@ -90,6 +90,7 @@ class OsvClient { JsonObject requestBody = new JsonObject(); requestBody.put("queries", queries); + boolean success = false; try { HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(5)) @@ -103,6 +104,7 @@ class OsvClient { HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == 200) { + success = true; JsonObject body = (JsonObject) Jsoner.deserialize(response.body()); JsonArray results = (JsonArray) body.get("results"); if (results != null) { @@ -148,14 +150,16 @@ class OsvClient { } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception e) { - // network errors — return whatever we have from cache + // network errors — return whatever we have from cache; leave entries uncached for retry } - // cache empty results for uncached entries that got no response - for (DependencyLoader.DepEntry entry : uncached) { - String gav = entry.display(); - result.putIfAbsent(gav, Collections.emptyList()); - CACHE.putIfAbsent(gav, Collections.emptyList()); + if (success) { + // cache empty results only for entries the API actually processed + for (DependencyLoader.DepEntry entry : uncached) { + String gav = entry.display(); + result.putIfAbsent(gav, Collections.emptyList()); + CACHE.putIfAbsent(gav, Collections.emptyList()); + } } return result; diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java index 975fb02b21e5..91d336ab245b 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java @@ -542,8 +542,7 @@ class RoutesTab extends AbstractTab { } // Normal table view - List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes); - sortedRoutes.sort(this::sortRoute); + List<RouteInfo> sortedRoutes = displayedRoutes(info); if (topPanelHeight < 0) { topPanelHeight = area.height() * 45 / 100; @@ -556,8 +555,6 @@ class RoutesTab extends AbstractTab { // Routes table Table routeTable; if (routeTopMode) { - sortedRoutes.sort(this::sortRouteTop); - List<Row> routeRows = new ArrayList<>(); for (RouteInfo route : sortedRoutes) { Style failStyle = route.failed > 0 @@ -1430,58 +1427,49 @@ class RoutesTab extends AbstractTab { // ---- Route actions ---- - String selectedRouteId() { - IntegrationInfo info = ctx.findSelectedIntegration(); - if (info == null || info.routes.isEmpty()) { - return null; + private List<RouteInfo> displayedRoutes(IntegrationInfo info) { + List<RouteInfo> sorted = new ArrayList<>(info.routes); + sorted.sort(this::sortRoute); + if (routeTopMode) { + sorted.sort(this::sortRouteTop); } - List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes); - sortedRoutes.sort(this::sortRoute); - Integer sel = routeTableState.selected(); - RouteInfo route = (sel != null && sel >= 0 && sel < sortedRoutes.size()) - ? sortedRoutes.get(sel) : sortedRoutes.get(0); - return route.routeId; + return sorted; } - private String selectedRouteState() { + private RouteInfo selectedRoute() { IntegrationInfo info = ctx.findSelectedIntegration(); if (info == null || info.routes.isEmpty()) { return null; } - List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes); - sortedRoutes.sort(this::sortRoute); + List<RouteInfo> sortedRoutes = displayedRoutes(info); Integer sel = routeTableState.selected(); - RouteInfo route = (sel != null && sel >= 0 && sel < sortedRoutes.size()) + return (sel != null && sel >= 0 && sel < sortedRoutes.size()) ? sortedRoutes.get(sel) : sortedRoutes.get(0); - return route.state; + } + + String selectedRouteId() { + RouteInfo route = selectedRoute(); + return route != null ? route.routeId : null; + } + + private String selectedRouteState() { + RouteInfo route = selectedRoute(); + return route != null ? route.state : null; } private boolean selectedRouteSupportsSuspension() { - IntegrationInfo info = ctx.findSelectedIntegration(); - if (info == null || info.routes.isEmpty()) { - return false; - } - List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes); - sortedRoutes.sort(this::sortRoute); - Integer sel = routeTableState.selected(); - RouteInfo route = (sel != null && sel >= 0 && sel < sortedRoutes.size()) - ? sortedRoutes.get(sel) : sortedRoutes.get(0); - return route.supportsSuspension; + RouteInfo route = selectedRoute(); + return route != null && route.supportsSuspension; } private void toggleRouteStartStop() { if (ctx.selectedPid == null) { return; } - IntegrationInfo info = ctx.findSelectedIntegration(); - if (info == null || info.routes.isEmpty()) { + RouteInfo route = selectedRoute(); + if (route == null) { return; } - List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes); - sortedRoutes.sort(this::sortRoute); - Integer sel = routeTableState.selected(); - RouteInfo route = (sel != null && sel >= 0 && sel < sortedRoutes.size()) - ? sortedRoutes.get(sel) : sortedRoutes.get(0); String command = "Started".equals(route.state) ? "stop" : "start"; sendRouteCommand(ctx.selectedPid, route.routeId, command); } @@ -1490,15 +1478,10 @@ class RoutesTab extends AbstractTab { if (ctx.selectedPid == null) { return; } - IntegrationInfo info = ctx.findSelectedIntegration(); - if (info == null || info.routes.isEmpty()) { + RouteInfo route = selectedRoute(); + if (route == null) { return; } - List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes); - sortedRoutes.sort(this::sortRoute); - Integer sel = routeTableState.selected(); - RouteInfo route = (sel != null && sel >= 0 && sel < sortedRoutes.size()) - ? sortedRoutes.get(sel) : sortedRoutes.get(0); String command = switch (route.state != null ? route.state : "") { case "Started" -> "suspend"; case "Suspended" -> "resume"; @@ -1549,16 +1532,11 @@ class RoutesTab extends AbstractTab { } private void loadSourceForSelectedRoute() { - IntegrationInfo info = ctx.findSelectedIntegration(); - if (info == null || info.routes.isEmpty()) { + RouteInfo selected = selectedRoute(); + if (selected == null) { return; } - List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes); - sortedRoutes.sort(this::sortRoute); - Integer sel = routeTableState.selected(); - RouteInfo selectedRoute = (sel != null && sel >= 0 && sel < sortedRoutes.size()) - ? sortedRoutes.get(sel) : sortedRoutes.get(0); - String routeId = selectedRoute.routeId; + String routeId = selected.routeId; sourceViewer.setOnLineSelected(sourceLine -> { sourceViewer.hide(); // Open drill-down diagram for this route @@ -1682,8 +1660,7 @@ class RoutesTab extends AbstractTab { if (info == null || info.routes.isEmpty()) { return null; } - List<RouteInfo> sorted = new ArrayList<>(info.routes); - sorted.sort(this::sortRoute); + List<RouteInfo> sorted = displayedRoutes(info); List<String> items = sorted.stream().map(r -> r.routeId != null ? r.routeId : "").toList(); Integer sel = routeTableState.selected(); return new SelectionContext("table", items, sel != null ? sel : -1, items.size(), "Routes"); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java index 8f0841c75558..b08ca2f5d675 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java @@ -21,6 +21,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledExecutorService; import dev.tamboui.layout.Constraint; @@ -93,7 +94,7 @@ class SendMessagePopup { private boolean showResponseHeaders; // History - private final List<SendHistoryEntry> history = new ArrayList<>(); + private final List<SendHistoryEntry> history = new CopyOnWriteArrayList<>(); private int historyIndex; // File chooser
