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;

Reply via email to