This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-23623 in repository https://gitbox.apache.org/repos/asf/camel.git
commit c9263e16e170ca40630ff6f0c3f898942df5e233 Author: Claus Ibsen <[email protected]> AuthorDate: Wed May 27 19:55:54 2026 +0200 CAMEL-23623: Enhance Errors tab in Camel TUI - Separate error data into dedicated {pid}-error.json file to keep status file lightweight - Status file only contains error metadata (count); full data loaded on demand when Errors tab is active - Add master/detail layout with sortable table (ID, AGO, ROUTE, NODE, HANDLED, EXCEPTION, MESSAGE) - Detail pane shows exception with unescaped stack trace, message history, properties, variables, headers, body - Add toggles: f=handled filter, p=properties, v=variables, h=headers, b=body, w=word-wrap, s=sort - Add Home/End keys for jumping to top/bottom of detail pane - Fix word-wrap content height underestimation in renderDetailPanel that prevented scrolling to bottom - Update camel get error CLI to read from separate error file Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../camel/cli/connector/LocalCliConnector.java | 30 +++- .../dsl/jbang/core/commands/CamelCommand.java | 4 + .../dsl/jbang/core/commands/process/ListError.java | 6 +- .../core/commands/process/ProcessBaseCommand.java | 13 ++ .../dsl/jbang/core/commands/tui/CamelMonitor.java | 155 ++++++++++++++------- .../dsl/jbang/core/commands/tui/ErrorsTab.java | 136 ++++++++++++++---- .../dsl/jbang/core/commands/tui/HistoryTab.java | 3 + .../jbang/core/commands/tui/IntegrationInfo.java | 1 + 8 files changed, 266 insertions(+), 82 deletions(-) diff --git a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java index a4ca0c6ccaf9..f3514777cebe 100644 --- a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java +++ b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java @@ -125,6 +125,7 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C private long traceFilePos; // keep track of trace offset private File messageHistoryFile; private File debugFile; + private File errorFile; private File receiveFile; private long receiveFilePos; // keep track of receive offset private byte[] lastSource; @@ -196,6 +197,7 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C outputFile = createLockFile(lockFile.getName() + "-output.json"); traceFile = createLockFile(lockFile.getName() + "-trace.json"); messageHistoryFile = createLockFile(lockFile.getName() + "-history.json"); + errorFile = createLockFile(lockFile.getName() + "-error.json"); debugFile = createLockFile(lockFile.getName() + "-debug.json"); receiveFile = createLockFile(lockFile.getName() + "-receive.json"); scheduledFuture = executor.scheduleWithFixedDelay(this::task, 0, delay, TimeUnit.MILLISECONDS); @@ -1389,7 +1391,13 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C JsonObject json = (JsonObject) dc26.call(DevConsole.MediaType.JSON, Map.of("stackTrace", "true")); if (json != null && !json.isEmpty()) { - root.put("errors", json); + // only include metadata in status file (full error data is in the error file) + JsonObject summary = new JsonObject(); + summary.put("enabled", json.get("enabled")); + summary.put("size", json.get("size")); + summary.put("maximumEntries", json.get("maximumEntries")); + summary.put("timeToLive", json.get("timeToLive")); + root.put("errors", summary); } } } @@ -1481,6 +1489,23 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C LOG.trace("Error updating message-history file: {} due to: {}. This exception is ignored.", messageHistoryFile, e.getMessage(), e); } + try { + DevConsole dc13c = camelContext.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class) + .resolveById("errors"); + if (dc13c != null) { + JsonObject json = (JsonObject) dc13c.call(DevConsole.MediaType.JSON, + Map.of("stackTrace", "true")); + if (json != null && !json.isEmpty()) { + LOG.trace("Updating error file: {}", errorFile); + String data = json.toJson() + System.lineSeparator(); + IOHelper.writeText(data, errorFile); + } + } + } catch (Exception e) { + // ignore + LOG.trace("Error updating error file: {} due to: {}. This exception is ignored.", + errorFile, e.getMessage(), e); + } try { DevConsole dc14 = camelContext.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class) .resolveById("receive"); @@ -1643,6 +1668,9 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C if (messageHistoryFile != null) { FileUtil.deleteFile(messageHistoryFile); } + if (errorFile != null) { + FileUtil.deleteFile(errorFile); + } if (debugFile != null) { FileUtil.deleteFile(debugFile); } diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java index fdf0c4b63574..4d675773de1d 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java @@ -119,6 +119,10 @@ public abstract class CamelCommand implements Callable<Integer> { return CommandLineHelper.getCamelDir().resolve(pid + "-trace.json"); } + public Path getErrorFile(String pid) { + return CommandLineHelper.getCamelDir().resolve(pid + "-error.json"); + } + public Path getReceiveFile(String pid) { return CommandLineHelper.getCamelDir().resolve(pid + "-receive.json"); } diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListError.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListError.java index 881eb3115ffa..b1ae97222215 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListError.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListError.java @@ -131,9 +131,9 @@ public class ListError extends ProcessWatchCommand { } String pid = Long.toString(ph.pid()); - JsonObject errors = (JsonObject) root.get("errors"); - if (errors != null) { - JsonArray arr = (JsonArray) errors.get("errors"); + JsonObject errorRoot = loadErrorFile(ph.pid()); + if (errorRoot != null) { + JsonArray arr = (JsonArray) errorRoot.get("errors"); if (arr != null) { for (Object o : arr) { JsonObject jo = (JsonObject) o; diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ProcessBaseCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ProcessBaseCommand.java index 92d8db2301e9..b2176c9bf49e 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ProcessBaseCommand.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ProcessBaseCommand.java @@ -105,6 +105,19 @@ abstract class ProcessBaseCommand extends CamelCommand { return null; } + JsonObject loadErrorFile(long pid) { + try { + Path f = getErrorFile(Long.toString(pid)); + if (f != null && Files.exists(f)) { + String text = Files.readString(f); + return (JsonObject) Jsoner.deserialize(text); + } + } catch (Exception e) { + // ignore + } + return null; + } + String sourceLocLine(String location) { while (StringHelper.countChar(location, ':') > 1) { location = location.substring(location.indexOf(':') + 1); 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 31201caef382..9850a845f6d2 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 @@ -736,6 +736,15 @@ public class CamelMonitor extends CamelCommand { if (tab == TAB_CIRCUIT_BREAKER) { circuitBreakerTab.onTabSelected(); } + if (tab == TAB_ERRORS && ctx.selectedPid != null) { + try { + long pid = Long.parseLong(ctx.selectedPid); + refreshErrorData(List.of(pid)); + } catch (NumberFormatException e) { + // ignore + } + errorsTab.onTabSelected(); + } tabsState.select(tab); return true; } @@ -1035,7 +1044,7 @@ public class CamelMonitor extends CamelCommand { } else if (cbCount > 0) { badgeTexts[TAB_CIRCUIT_BREAKER] = "(" + cbCount + ")"; } - int errorCount = hasSelection ? sel.errors.size() : 0; + int errorCount = hasSelection ? sel.errorCount : 0; if (errorCount > 0) { badgeTexts[TAB_ERRORS] = "(" + errorCount + ")"; badgeStyles[TAB_ERRORS] = red; @@ -2012,6 +2021,11 @@ public class CamelMonitor extends CamelCommand { } } + // Refresh error data only when the Errors tab is visible + if (tabsState.selected() == TAB_ERRORS) { + refreshErrorData(pids); + } + // Refresh trace data only when the Inspect tab is visible if (tabsState.selected() == TAB_HISTORY) { refreshTraceData(pids); @@ -3009,58 +3023,10 @@ public class CamelMonitor extends CamelCommand { } } - // Parse errors from error registry + // Parse error count from error registry (full error data is loaded on demand by ErrorsTab) JsonObject errorsObj = (JsonObject) root.get("errors"); if (errorsObj != null) { - JsonArray errorList = (JsonArray) errorsObj.get("errors"); - if (errorList != null) { - for (Object e : errorList) { - JsonObject ej = (JsonObject) e; - ErrorInfo ei = new ErrorInfo(); - ei.routeId = ej.getString("routeId"); - ei.nodeId = ej.getString("nodeId"); - ei.exchangeId = ej.getString("exchangeId"); - ei.handled = ej.getBooleanOrDefault("handled", false); - Long ts = ej.getLong("timestamp"); - if (ts != null) { - ei.timestamp = ts; - } - ei.location = ej.getString("location"); - ei.threadName = ej.getString("threadName"); - Long elapsed = ej.getLong("elapsed"); - if (elapsed != null) { - ei.elapsed = elapsed; - } - ei.endpointUri = ej.getString("endpointUri"); - ei.fromEndpointUri = ej.getString("fromEndpointUri"); - // exception - JsonObject ex = (JsonObject) ej.get("exception"); - if (ex != null) { - ei.exceptionType = ex.getString("type"); - ei.exceptionMessage = ex.getString("message"); - ei.stackTrace = ex.getString("stackTrace"); - } - // message history - Object mhObj = ej.get("messageHistory"); - if (mhObj instanceof JsonArray mhArr) { - ei.messageHistory = new String[mhArr.size()]; - for (int i = 0; i < mhArr.size(); i++) { - ei.messageHistory[i] = mhArr.get(i).toString(); - } - } - // message (body, headers) - JsonObject msg = (JsonObject) ej.get("message"); - if (msg != null) { - ei.body = msg.getString("body"); - ei.bodyType = msg.getString("bodyType"); - parseKvArray(msg.getCollection("headers"), ei.headers, ei.headerTypes); - } - // exchange properties and variables - parseKvArray(ej.getCollection("exchangeProperties"), ei.properties, ei.propertyTypes); - parseKvArray(ej.getCollection("exchangeVariables"), ei.variables, ei.variableTypes); - info.errors.add(ei); - } - } + info.errorCount = errorsObj.getIntegerOrDefault("size", 0); } // Parse REST DSL services @@ -3192,6 +3158,93 @@ public class CamelMonitor extends CamelCommand { } } + private JsonObject loadErrorFile(long pid) { + return TuiHelper.loadStatus(pid, this::getErrorFile); + } + + private void refreshErrorData(List<Long> pids) { + IntegrationInfo sel = findSelectedIntegration(); + if (sel == null) { + return; + } + try { + long pid = Long.parseLong(sel.pid); + JsonObject root = loadErrorFile(pid); + if (root == null) { + return; + } + JsonArray errorList = (JsonArray) root.get("errors"); + if (errorList == null) { + return; + } + List<ErrorInfo> parsed = new ArrayList<>(); + for (Object e : errorList) { + JsonObject ej = (JsonObject) e; + ErrorInfo ei = new ErrorInfo(); + ei.routeId = ej.getString("routeId"); + ei.nodeId = ej.getString("nodeId"); + ei.exchangeId = ej.getString("exchangeId"); + ei.handled = Boolean.TRUE.equals(ej.get("handled")); + Long ts = ej.getLong("timestamp"); + if (ts != null) { + ei.timestamp = ts; + } + ei.location = ej.getString("location"); + ei.threadName = ej.getString("threadName"); + Long elapsed = ej.getLong("elapsed"); + if (elapsed != null) { + ei.elapsed = elapsed; + } + ei.endpointUri = ej.getString("endpointUri"); + ei.fromEndpointUri = ej.getString("fromEndpointUri"); + // exception + JsonObject ex = (JsonObject) ej.get("exception"); + if (ex != null) { + ei.exceptionType = ex.getString("type"); + ei.exceptionMessage = ex.getString("message"); + ei.stackTrace = ex.getString("stackTrace"); + } + // message history + Object mhObj = ej.get("messageHistory"); + if (mhObj instanceof JsonArray mhArr) { + ei.messageHistory = new String[mhArr.size()]; + for (int i = 0; i < mhArr.size(); i++) { + ei.messageHistory[i] = mhArr.get(i).toString(); + } + } + // message (body, headers) + JsonObject msg = (JsonObject) ej.get("message"); + if (msg != null) { + Object bodyObj = msg.get("body"); + if (bodyObj instanceof JsonObject bodyJson) { + ei.body = bodyJson.getString("value"); + ei.bodyType = bodyJson.getString("type"); + } else if (bodyObj != null) { + ei.body = bodyObj.toString(); + } + JsonArray hdrs = msg.getCollection("headers"); + if (hdrs != null) { + parseKvArray(hdrs, ei.headers, ei.headerTypes); + } + } + // exchange properties and variables + JsonArray props = ej.getCollection("exchangeProperties"); + if (props != null) { + parseKvArray(props, ei.properties, ei.propertyTypes); + } + JsonArray vars = ej.getCollection("exchangeVariables"); + if (vars != null) { + parseKvArray(vars, ei.variables, ei.variableTypes); + } + parsed.add(ei); + } + sel.errors.clear(); + sel.errors.addAll(parsed); + } catch (Exception e) { + // ignore + } + } + // ---- Helpers ---- private IntegrationInfo findSelectedIntegration() { diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java index c74e3c7ceb39..9f5d9663aff6 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java @@ -35,22 +35,30 @@ import dev.tamboui.widgets.table.Cell; import dev.tamboui.widgets.table.Row; import dev.tamboui.widgets.table.Table; import dev.tamboui.widgets.table.TableState; +import org.apache.camel.util.json.Jsoner; import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*; class ErrorsTab implements MonitorTab { - private static final String[] SORT_COLUMNS = { "age", "route", "exception" }; + private static final String[] SORT_COLUMNS = { "id", "age", "route", "node", "exception" }; private final MonitorContext ctx; private final TableState tableState = new TableState(); private final ScrollbarState detailScrollState = new ScrollbarState(); - private String sort = "age"; + private String sort = "id"; private int sortIndex; private boolean sortReversed; + private static final String[] HANDLED_FILTER = { "all", "true", "false" }; + private int handledIndex; + private String handledFilter = "all"; private int detailScroll; private int detailHScroll; private boolean wordWrap = true; + private boolean showProperties; + private boolean showVariables; + private boolean showHeaders = true; + private boolean showBody = true; ErrorsTab(MonitorContext ctx) { this.ctx = ctx; @@ -80,6 +88,35 @@ class ErrorsTab implements MonitorTab { wordWrap = !wordWrap; return true; } + if (ke.isCharIgnoreCase('f')) { + handledIndex = (handledIndex + 1) % HANDLED_FILTER.length; + handledFilter = HANDLED_FILTER[handledIndex]; + return true; + } + if (ke.isCharIgnoreCase('p')) { + showProperties = !showProperties; + return true; + } + if (ke.isCharIgnoreCase('v')) { + showVariables = !showVariables; + return true; + } + if (ke.isCharIgnoreCase('h')) { + showHeaders = !showHeaders; + return true; + } + if (ke.isCharIgnoreCase('b')) { + showBody = !showBody; + return true; + } + if (ke.isHome()) { + detailScroll = 0; + return true; + } + if (ke.isEnd()) { + detailScroll = Integer.MAX_VALUE; + return true; + } if (ke.isPageUp()) { detailScroll = Math.max(0, detailScroll - 5); return true; @@ -113,8 +150,7 @@ class ErrorsTab implements MonitorTab { @Override public void navigateDown() { detailScroll = 0; - IntegrationInfo info = ctx.findSelectedIntegration(); - tableState.selectNext(info != null ? info.errors.size() : 0); + tableState.selectNext(filteredSize()); } @Override @@ -125,8 +161,7 @@ class ErrorsTab implements MonitorTab { return; } - List<ErrorInfo> sorted = new ArrayList<>(info.errors); - sorted.sort(this::sortError); + List<ErrorInfo> sorted = applyFilter(info.errors); List<Row> rows = new ArrayList<>(); for (ErrorInfo ei : sorted) { @@ -138,6 +173,7 @@ class ErrorsTab implements MonitorTab { String shortException = shortExceptionType(ei.exceptionType); rows.add(Row.from( + Cell.from(ei.exchangeId != null ? ei.exchangeId : ""), Cell.from(ago), Cell.from(Span.styled(ei.routeId != null ? ei.routeId : "", Style.EMPTY.fg(Color.CYAN))), Cell.from(ei.nodeId != null ? ei.nodeId : ""), @@ -150,7 +186,7 @@ class ErrorsTab implements MonitorTab { rows.add(Row.from( Cell.from(Span.styled("No errors captured", Style.EMPTY.dim())), Cell.from(""), Cell.from(""), Cell.from(""), - Cell.from(""), Cell.from(""))); + Cell.from(""), Cell.from(""), Cell.from(""))); } ErrorInfo selectedError = null; @@ -161,20 +197,22 @@ class ErrorsTab implements MonitorTab { boolean showDetail = selectedError != null; List<Rect> chunks = showDetail ? Layout.vertical() - .constraints(Constraint.length(Math.min(sorted.size() + 3, 12)), Constraint.fill()) + .constraints(Constraint.length(13), Constraint.length(1), Constraint.fill()) .split(area) : List.of(area); Table table = Table.builder() .rows(rows) .header(Row.from( + Cell.from(Span.styled(sortLabel("ID", "id"), sortStyle("id"))), Cell.from(Span.styled(sortLabel("AGO", "age"), sortStyle("age"))), Cell.from(Span.styled(sortLabel("ROUTE", "route"), sortStyle("route"))), - Cell.from(Span.styled("NODE", Style.EMPTY.bold())), + Cell.from(Span.styled(sortLabel("NODE", "node"), sortStyle("node"))), Cell.from(Span.styled("HANDLED", Style.EMPTY.bold())), Cell.from(Span.styled(sortLabel("EXCEPTION", "exception"), sortStyle("exception"))), Cell.from(Span.styled("MESSAGE", Style.EMPTY.bold())))) .widths( + Constraint.length(38), Constraint.length(8), Constraint.length(20), Constraint.length(20), @@ -189,7 +227,7 @@ class ErrorsTab implements MonitorTab { frame.renderStatefulWidget(table, chunks.get(0), tableState); if (showDetail) { - renderDetail(frame, chunks.get(1), selectedError); + renderDetail(frame, chunks.get(2), selectedError); } } @@ -197,9 +235,18 @@ class ErrorsTab implements MonitorTab { public void renderFooter(List<Span> spans) { hint(spans, "Esc", "back"); hint(spans, "↑↓", "navigate"); + hint(spans, "PgUp/Dn", "scroll detail"); + if (!wordWrap) { + hint(spans, "←→", "h-scroll"); + } + hint(spans, "Home/End", "top/end"); hint(spans, "s", "sort"); - hint(spans, "w", "wrap"); - hint(spans, "PgUp/Dn", "scroll"); + hint(spans, "f", "handled [" + handledFilter + "]"); + hint(spans, "p", "properties [" + (showProperties ? "on" : "off") + "]"); + hint(spans, "v", "variables [" + (showVariables ? "on" : "off") + "]"); + hint(spans, "h", "headers [" + (showHeaders ? "on" : "off") + "]"); + hint(spans, "b", "body [" + (showBody ? "on" : "off") + "]"); + hint(spans, "w", "wrap [" + (wordWrap ? "on" : "off") + "]"); hint(spans, "1-0", "tabs"); } @@ -216,10 +263,22 @@ class ErrorsTab implements MonitorTab { StringBuilder sb = new StringBuilder(); sb.append(ei.exceptionType); if (ei.exceptionMessage != null) { - sb.append(": ").append(ei.exceptionMessage); + String msg = ei.exceptionMessage; + try { + msg = Jsoner.unescape(msg); + } catch (Exception e) { + // ignore + } + sb.append(": ").append(msg); } if (ei.stackTrace != null) { - sb.append("\n").append(ei.stackTrace); + String st = ei.stackTrace; + try { + st = Jsoner.unescape(st); + } catch (Exception e) { + // ignore + } + sb.append("\n").append(st); } exception = sb.toString(); } @@ -229,24 +288,24 @@ class ErrorsTab implements MonitorTab { if (ei.messageHistory != null && ei.messageHistory.length > 0) { lines.add(Line.from(Span.styled(" Message History:", Style.EMPTY.fg(Color.MAGENTA).bold()))); for (String step : ei.messageHistory) { - lines.add(Line.from(Span.raw(" " + step))); + lines.add(Line.from(Span.raw(" " + TuiHelper.fixControlChars(step)))); } lines.add(Line.from(Span.raw(""))); } - // variables, properties, headers - if (!ei.variables.isEmpty()) { - HistoryTab.addKvLines(lines, " Variables:", ei.variables, ei.variableTypes); + // exchange properties, variables, headers, body + if (showProperties && !ei.properties.isEmpty()) { + HistoryTab.addKvLines(lines, " Exchange Properties:", ei.properties, ei.propertyTypes); } - if (!ei.properties.isEmpty()) { - HistoryTab.addKvLines(lines, " Properties:", ei.properties, ei.propertyTypes); + if (showVariables && !ei.variables.isEmpty()) { + HistoryTab.addKvLines(lines, " Exchange Variables:", ei.variables, ei.variableTypes); } - if (!ei.headers.isEmpty()) { + if (showHeaders && !ei.headers.isEmpty()) { HistoryTab.addKvLines(lines, " Headers:", ei.headers, ei.headerTypes); } - - // body - HistoryTab.addBodyLines(lines, ei.body, ei.bodyType); + if (showBody) { + HistoryTab.addBodyLines(lines, ei.body, ei.bodyType); + } int[] scroll = { detailScroll }; int[] hScroll = { detailHScroll }; @@ -265,7 +324,9 @@ class ErrorsTab implements MonitorTab { private int sortError(ErrorInfo a, ErrorInfo b) { int result = switch (sort) { + case "id" -> compareStr(a.exchangeId, b.exchangeId); case "route" -> compareStr(a.routeId, b.routeId); + case "node" -> compareStr(a.nodeId, b.nodeId); case "exception" -> compareStr(a.exceptionType, b.exceptionType); default -> Long.compare(b.timestamp, a.timestamp); // newest first }; @@ -289,12 +350,33 @@ class ErrorsTab implements MonitorTab { if (info == null || info.errors.isEmpty()) { return null; } - List<ErrorInfo> sorted = new ArrayList<>(info.errors); - sorted.sort(this::sortError); - List<String> items = sorted.stream() + List<ErrorInfo> filtered = applyFilter(info.errors); + List<String> items = filtered.stream() .map(e -> e.exchangeId != null ? e.exchangeId : "") .toList(); Integer sel = tableState.selected(); return new SelectionContext("table", items, sel != null ? sel : -1, items.size(), "Errors"); } + + private List<ErrorInfo> applyFilter(List<ErrorInfo> errors) { + List<ErrorInfo> list = new ArrayList<>(errors); + list.sort(this::sortError); + if (!"all".equals(handledFilter)) { + boolean filterVal = "true".equals(handledFilter); + list.removeIf(e -> e.handled != filterVal); + } + return list; + } + + private int filteredSize() { + IntegrationInfo info = ctx.findSelectedIntegration(); + if (info == null) { + return 0; + } + if ("all".equals(handledFilter)) { + return info.errors.size(); + } + boolean filterVal = "true".equals(handledFilter); + return (int) info.errors.stream().filter(e -> e.handled == filterVal).count(); + } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java index 668bfce4bbbf..19021e2f9dab 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java @@ -844,6 +844,9 @@ class HistoryTab implements MonitorTab { int w = l.width(); contentHeight += Math.max(1, (w + visibleWidth - 1) / visibleWidth); } + // word-wrap breaks at word boundaries which can produce more lines + // than char-based math; add padding so last section is always reachable + contentHeight += visibleHeight; } else { contentHeight = lines.size(); } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java index ffaa18c73fbb..099e7b2294ca 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java @@ -62,6 +62,7 @@ class IntegrationInfo { final List<HealthCheckInfo> healthChecks = new ArrayList<>(); final List<EndpointInfo> endpoints = new ArrayList<>(); final List<CircuitBreakerInfo> circuitBreakers = new ArrayList<>(); + int errorCount; final List<ErrorInfo> errors = new ArrayList<>(); final List<HttpEndpointInfo> httpEndpoints = new ArrayList<>(); String httpServer;
