You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/05/16 12:04:40 UTC
(camel) 01/01: TUI: add Consumers tab (tab 4) after Routes
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 <cl...@gmail.com>
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 <no...@anthropic.com>
---
.../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;