This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/camel-tui-consumers-tab in repository https://gitbox.apache.org/repos/asf/camel.git
commit 44ba3a321b3cdac261c696ff5985813278a60e06 Author: Claus Ibsen <[email protected]> AuthorDate: Sat May 16 14:04:28 2026 +0200 TUI: add Consumers tab (tab 4) after Routes - Insert new TAB_CONSUMERS = 3, shifting Endpoints→5, Health→6, History→7, Trace→8 (NUM_TABS 7→8) - Parse consumers JSON (id, uri, state, class, scheduled, inflight, polling, totalCounter, delay/period, statistics timestamps) - renderConsumers() table: ID, STATUS, TYPE, INFLIGHT, TOTAL, PERIOD, SINCE-LAST (started/completed/failed), URI - STATUS shown in green (Started/Polling) or red; TYPE strips "Consumer" suffix and package prefix for readability - Sort with 's' key cycling ID→STATUS→TYPE→INFLIGHT→TOTAL→URI - Footer hints updated; all number hints updated to 1-8 Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --- .../dsl/jbang/core/commands/tui/CamelMonitor.java | 264 +++++++++++++++++++-- 1 file changed, 246 insertions(+), 18 deletions(-) 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 1fe15c28d78c..c08067eec3e9 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 @@ -106,16 +106,17 @@ public class CamelMonitor extends CamelCommand { private static final int MAX_SPARKLINE_POINTS = 60; private static final int MAX_LOG_LINES = 5000; private static final int MAX_TRACES = 200; - private static final int NUM_TABS = 7; + private static final int NUM_TABS = 8; // Tab indices private static final int TAB_OVERVIEW = 0; private static final int TAB_LOG = 1; private static final int TAB_ROUTES = 2; - private static final int TAB_ENDPOINTS = 3; - private static final int TAB_HEALTH = 4; - private static final int TAB_HISTORY = 5; - private static final int TAB_TRACE = 6; + private static final int TAB_CONSUMERS = 3; + private static final int TAB_ENDPOINTS = 4; + private static final int TAB_HEALTH = 5; + private static final int TAB_HISTORY = 6; + private static final int TAB_TRACE = 7; // Overview sort columns private static final String[] OVERVIEW_SORT_COLUMNS = { "pid", "name", "status", "total", "fail" }; @@ -123,6 +124,9 @@ public class CamelMonitor extends CamelCommand { // Route sort columns private static final String[] ROUTE_SORT_COLUMNS = { "total", "failed", "name", "status" }; + // Consumer sort columns (order matches table column order) + private static final String[] CONSUMER_SORT_COLUMNS = { "id", "status", "type", "inflight", "total", "uri" }; + // Endpoint sort columns (order matches table column order) private static final String[] ENDPOINT_SORT_COLUMNS = { "component", "route", "dir", "total", "uri" }; @@ -139,6 +143,7 @@ public class CamelMonitor extends CamelCommand { private final Map<String, VanishingInfo> vanishing = new ConcurrentHashMap<>(); private final TableState overviewTableState = new TableState(); private final TableState routeTableState = new TableState(); + private final TableState consumerTableState = new TableState(); private final TableState healthTableState = new TableState(); private final TableState endpointTableState = new TableState(); private final TableState processorTableState = new TableState(); @@ -160,6 +165,10 @@ public class CamelMonitor extends CamelCommand { private String routeSort = "name"; private int routeSortIndex = 2; + // Consumer sort state (default: id = index 0) + private String consumerSort = "id"; + private int consumerSortIndex = 0; + // Endpoint sort state (default: route = index 1) private String endpointSort = "route"; private int endpointSortIndex = 1; @@ -314,15 +323,18 @@ public class CamelMonitor extends CamelCommand { return handleTabKey(TAB_ROUTES); } if (ke.isChar('4')) { - return handleTabKey(TAB_ENDPOINTS); + return handleTabKey(TAB_CONSUMERS); } if (ke.isChar('5')) { - return handleTabKey(TAB_HEALTH); + return handleTabKey(TAB_ENDPOINTS); } if (ke.isChar('6')) { - return handleTabKey(TAB_HISTORY); + return handleTabKey(TAB_HEALTH); } if (ke.isChar('7')) { + return handleTabKey(TAB_HISTORY); + } + if (ke.isChar('8')) { return handleTabKey(TAB_TRACE); } @@ -435,6 +447,13 @@ public class CamelMonitor extends CamelCommand { return true; } + // Consumers tab: sort + if (tab == TAB_CONSUMERS && ke.isCharIgnoreCase('s')) { + consumerSortIndex = (consumerSortIndex + 1) % CONSUMER_SORT_COLUMNS.length; + consumerSort = CONSUMER_SORT_COLUMNS[consumerSortIndex]; + return true; + } + // Endpoints tab: sort and filter if (tab == TAB_ENDPOINTS && ke.isCharIgnoreCase('s')) { endpointSortIndex = (endpointSortIndex + 1) % ENDPOINT_SORT_COLUMNS.length; @@ -683,6 +702,7 @@ public class CamelMonitor extends CamelCommand { switch (tabsState.selected()) { case TAB_OVERVIEW -> overviewTableState.selectPrevious(); case TAB_ROUTES -> routeTableState.selectPrevious(); + case TAB_CONSUMERS -> consumerTableState.selectPrevious(); case TAB_HEALTH -> healthTableState.selectPrevious(); case TAB_ENDPOINTS -> endpointTableState.selectPrevious(); case TAB_LOG -> { @@ -712,6 +732,10 @@ public class CamelMonitor extends CamelCommand { IntegrationInfo info = findSelectedIntegration(); routeTableState.selectNext(info != null ? info.routes.size() : 0); } + case TAB_CONSUMERS -> { + IntegrationInfo info = findSelectedIntegration(); + consumerTableState.selectNext(info != null ? info.consumers.size() : 0); + } case TAB_HEALTH -> { IntegrationInfo info = findSelectedIntegration(); healthTableState.selectNext(info != null ? getFilteredHealthChecks(info).size() : 0); @@ -792,6 +816,7 @@ public class CamelMonitor extends CamelCommand { IntegrationInfo sel = findSelectedIntegration(); boolean hasSelection = selectedPid != null && sel != null; int routeCount = hasSelection ? sel.routes.size() : 0; + int consumerCount = hasSelection ? sel.consumers.size() : 0; int endpointCount = hasSelection ? sel.endpoints.size() : 0; int healthCount = hasSelection ? sel.healthChecks.size() : 0; int historyCount = hasSelection ? historyEntries.size() : 0; @@ -802,13 +827,14 @@ public class CamelMonitor extends CamelCommand { badge(" 1 Overview ", activeCount), Line.from(" 2 Log "), badge(" 3 Routes ", routeCount), - badge(" 4 Endpoints ", endpointCount), - badge(" 5 Health ", healthCount), - badge(" 6 History ", historyCount), + badge(" 4 Consumers ", consumerCount), + badge(" 5 Endpoints ", endpointCount), + badge(" 6 Health ", healthCount), + badge(" 7 History ", historyCount), hasTraces - ? Line.from(Span.raw(" 7 Trace "), Span.styled("(*)", Style.EMPTY.fg(Color.YELLOW).bold()), + ? Line.from(Span.raw(" 8 Trace "), Span.styled("(*)", Style.EMPTY.fg(Color.YELLOW).bold()), Span.raw(" ")) - : Line.from(" 7 Trace ")) + : Line.from(" 8 Trace ")) .highlightStyle(Style.EMPTY.fg(Color.rgb(0xF6, 0x91, 0x23)).bold()) .divider(Span.styled(" | ", Style.EMPTY.dim())) .build(); @@ -824,6 +850,7 @@ public class CamelMonitor extends CamelCommand { switch (tabsState.selected()) { case TAB_OVERVIEW -> renderOverview(frame, area); case TAB_ROUTES -> renderRoutes(frame, area); + case TAB_CONSUMERS -> renderConsumers(frame, area); case TAB_HEALTH -> renderHealth(frame, area); case TAB_ENDPOINTS -> renderEndpoints(frame, area); case TAB_LOG -> renderLog(frame, area); @@ -1153,6 +1180,147 @@ public class CamelMonitor extends CamelCommand { return sortStyle(column, routeSort); } + private String consumerSortLabel(String label, String column) { + return sortLabel(label, column, consumerSort); + } + + private Style consumerSortStyle(String column) { + return sortStyle(column, consumerSort); + } + + private int sortConsumer(ConsumerInfo a, ConsumerInfo b) { + return switch (consumerSort) { + case "status" -> { + String sa = consumerStatus(a); + String sb = consumerStatus(b); + yield sa.compareToIgnoreCase(sb); + } + case "type" -> { + String ta = consumerType(a); + String tb = consumerType(b); + yield ta.compareToIgnoreCase(tb); + } + case "inflight" -> Integer.compare(b.inflight, a.inflight); + case "total" -> { + long la = a.totalCounter != null ? a.totalCounter : 0; + long lb = b.totalCounter != null ? b.totalCounter : 0; + yield Long.compare(lb, la); + } + case "uri" -> { + String ua = a.uri != null ? a.uri : ""; + String ub = b.uri != null ? b.uri : ""; + yield ua.compareToIgnoreCase(ub); + } + default -> { // "id" + String ia = a.id != null ? a.id : ""; + String ib = b.id != null ? b.id : ""; + yield ia.compareToIgnoreCase(ib); + } + }; + } + + private static String consumerStatus(ConsumerInfo ci) { + if (ci.polling != null && ci.polling) { + return "Polling"; + } + return ci.state != null ? ci.state : ""; + } + + private static String consumerType(ConsumerInfo ci) { + if (ci.className == null) { + return ""; + } + String s = ci.className; + if (s.endsWith("Consumer")) { + s = s.substring(0, s.length() - 8); + } + int dot = s.lastIndexOf('.'); + return dot >= 0 ? s.substring(dot + 1) : s; + } + + private static String consumerPeriod(ConsumerInfo ci) { + if (ci.period != null) { + return ci.period + "ms"; + } else if (ci.delay != null) { + return ci.delay + "ms"; + } + return ""; + } + + private static String consumerSinceLast(ConsumerInfo ci) { + String s1 = ci.sinceLastStarted != null ? ci.sinceLastStarted : "-"; + String s2 = ci.sinceLastCompleted != null ? ci.sinceLastCompleted : "-"; + String s3 = ci.sinceLastFailed != null ? ci.sinceLastFailed : "-"; + return s1 + "/" + s2 + "/" + s3; + } + + private void renderConsumers(Frame frame, Rect area) { + IntegrationInfo info = findSelectedIntegration(); + if (info == null) { + renderNoSelection(frame, area); + return; + } + + List<ConsumerInfo> sorted = new ArrayList<>(info.consumers); + sorted.sort(this::sortConsumer); + + List<Row> rows = new ArrayList<>(); + for (ConsumerInfo ci : sorted) { + String status = consumerStatus(ci); + Style statusStyle = "Started".equals(ci.state) || "Polling".equals(status) + ? Style.EMPTY.fg(Color.GREEN) + : Style.EMPTY.fg(Color.RED); + String type = consumerType(ci); + String period = consumerPeriod(ci); + String sinceLast = consumerSinceLast(ci); + + rows.add(Row.from( + Cell.from(Span.styled(ci.id != null ? ci.id : "", Style.EMPTY.fg(Color.CYAN))), + Cell.from(Span.styled(status, statusStyle)), + Cell.from(type), + rightCell(ci.inflight > 0 ? String.valueOf(ci.inflight) : "", 8), + rightCell(ci.totalCounter != null ? String.valueOf(ci.totalCounter) : "", 8), + Cell.from(period), + Cell.from(sinceLast), + Cell.from(ci.uri != null ? ci.uri : ""))); + } + + if (rows.isEmpty()) { + rows.add(Row.from( + Cell.from(Span.styled("No consumers", Style.EMPTY.dim())), + Cell.from(""), Cell.from(""), Cell.from(""), + Cell.from(""), Cell.from(""), Cell.from(""), Cell.from(""))); + } + + Table table = Table.builder() + .rows(rows) + .header(Row.from( + Cell.from(Span.styled(consumerSortLabel("ID", "id"), consumerSortStyle("id"))), + Cell.from(Span.styled(consumerSortLabel("STATUS", "status"), consumerSortStyle("status"))), + Cell.from(Span.styled(consumerSortLabel("TYPE", "type"), consumerSortStyle("type"))), + rightCell(consumerSortLabel("INFLIGHT", "inflight"), 8, consumerSortStyle("inflight")), + rightCell(consumerSortLabel("TOTAL", "total"), 8, consumerSortStyle("total")), + Cell.from(Span.styled("PERIOD", Style.EMPTY.bold())), + Cell.from(Span.styled("SINCE-LAST", Style.EMPTY.bold())), + Cell.from(Span.styled(consumerSortLabel("URI", "uri"), consumerSortStyle("uri"))))) + .widths( + Constraint.length(20), + Constraint.length(10), + Constraint.length(20), + Constraint.length(8), + Constraint.length(8), + Constraint.length(10), + Constraint.length(22), + Constraint.fill()) + .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue()) + .highlightSpacing(Table.HighlightSpacing.ALWAYS) + .block(Block.builder().borderType(BorderType.ROUNDED) + .title(" Consumers sort:" + consumerSort + " ").build()) + .build(); + + frame.renderStatefulWidget(table, area, consumerTableState); + } + private String endpointSortLabel(String label, String column) { return sortLabel(label, column, endpointSort); } @@ -2700,7 +2868,7 @@ public class CamelMonitor extends CamelCommand { if (selectedPid != null) { hint(spans, "Esc", "unselect"); } - hint(spans, "1-7", "tabs"); + hint(spans, "1-8", "tabs"); } else if (tab == TAB_ROUTES && showDiagram) { String closeKey = diagramTextMode ? "D" : "d"; hint(spans, closeKey + "/Esc", "close"); @@ -2719,18 +2887,23 @@ public class CamelMonitor extends CamelCommand { hint(spans, "s", "sort"); hint(spans, "d", "diagram"); hint(spans, "D", "text diagram"); - hint(spans, "1-7", "tabs"); + hint(spans, "1-8", "tabs"); + } else if (tab == TAB_CONSUMERS) { + hint(spans, "Esc", "back"); + hint(spans, "\u2191\u2193", "navigate"); + hint(spans, "s", "sort"); + hint(spans, "1-8", "tabs"); } else if (tab == TAB_ENDPOINTS) { hint(spans, "Esc", "back"); hint(spans, "\u2191\u2193", "navigate"); hint(spans, "s", "sort"); hint(spans, "r", "remote" + (showOnlyRemote ? " [on]" : " [off]")); - hint(spans, "1-7", "tabs"); + hint(spans, "1-8", "tabs"); } else if (tab == TAB_HEALTH) { hint(spans, "Esc", "back"); hint(spans, "\u2191\u2193", "navigate"); hint(spans, "d", "toggle DOWN"); - hint(spans, "1-7", "tabs"); + hint(spans, "1-8", "tabs"); } else if (tab == TAB_LOG) { hint(spans, "Esc", "back"); hint(spans, "\u2191\u2193", "scroll"); @@ -2775,7 +2948,7 @@ public class CamelMonitor extends CamelCommand { } else { hint(spans, "Esc", "back"); hint(spans, "\u2191\u2193", "navigate"); - hint(spans, "1-7", "tabs"); + hint(spans, "1-8", "tabs"); } frame.renderWidget(Paragraph.from(Line.from(spans)), area); @@ -3518,6 +3691,44 @@ public class CamelMonitor extends CamelCommand { } } + // Parse consumers + JsonObject consumersObj = (JsonObject) root.get("consumers"); + if (consumersObj != null) { + JsonArray consumerList = (JsonArray) consumersObj.get("consumers"); + if (consumerList != null) { + for (Object c : consumerList) { + JsonObject cj = (JsonObject) c; + ConsumerInfo ci = new ConsumerInfo(); + ci.id = cj.getString("id"); + ci.uri = cj.getString("uri"); + ci.state = cj.getString("state"); + ci.className = cj.getString("class"); + ci.scheduled = Boolean.TRUE.equals(cj.get("scheduled")); + ci.inflight = cj.getIntegerOrDefault("inflight", 0); + ci.polling = (Boolean) cj.get("polling"); + ci.totalCounter = cj.getLong("totalCounter"); + ci.delay = cj.getLong("delay"); + ci.period = cj.getLong("period"); + JsonObject cStats = (JsonObject) cj.get("statistics"); + if (cStats != null) { + Object last = cStats.get("lastCreatedExchangeTimestamp"); + if (last != null) { + ci.sinceLastStarted = TimeUtils.printSince(Long.parseLong(last.toString())); + } + last = cStats.get("lastCompletedExchangeTimestamp"); + if (last != null) { + ci.sinceLastCompleted = TimeUtils.printSince(Long.parseLong(last.toString())); + } + last = cStats.get("lastFailedExchangeTimestamp"); + if (last != null) { + ci.sinceLastFailed = TimeUtils.printSince(Long.parseLong(last.toString())); + } + } + info.consumers.add(ci); + } + } + } + // Parse endpoints (top-level "endpoints" is a JsonObject with nested "endpoints" array) JsonObject endpointsObj = (JsonObject) root.get("endpoints"); if (endpointsObj != null) { @@ -3669,6 +3880,7 @@ public class CamelMonitor extends CamelCommand { boolean vanishing; long vanishStart; final List<RouteInfo> routes = new ArrayList<>(); + final List<ConsumerInfo> consumers = new ArrayList<>(); final List<HealthCheckInfo> healthChecks = new ArrayList<>(); final List<EndpointInfo> endpoints = new ArrayList<>(); } @@ -3713,6 +3925,22 @@ public class CamelMonitor extends CamelCommand { String message; } + static class ConsumerInfo { + String id; + String uri; + String state; + String className; + boolean scheduled; + int inflight; + Boolean polling; + Long totalCounter; + Long delay; + Long period; + String sinceLastStarted; + String sinceLastCompleted; + String sinceLastFailed; + } + static class EndpointInfo { String uri; String component;
