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


The following commit(s) were added to refs/heads/main by this push:
     new d9d387ba551d camel-jbang - TUI top mode, diagram toggle, and Shift+Tab 
navigation (#23358)
d9d387ba551d is described below

commit d9d387ba551dc922bb8ea04d05fd9dab525b8f88
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed May 20 11:17:05 2026 +0200

    camel-jbang - TUI top mode, diagram toggle, and Shift+Tab navigation 
(#23358)
    
    * camel-jbang - TUI diagram all/single route toggle
    
    Add 'a' key on route tab to toggle between showing all routes or just the
    selected route in the diagram view. Route titles are rendered in bold in
    text diagram mode, and routes are separated by double empty lines.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * camel-jbang - TUI top mode for routes and processors with sortable columns
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * camel-jbang - TUI top mode processor table only shows processors
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * camel-jbang - TUI Shift+Tab cycles tabs backwards
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * camel-jbang - TUI top mode inline bar chart, tab label, and sort 
refinements
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  | 636 +++++++++++++++------
 1 file changed, 462 insertions(+), 174 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 9090fbc6be38..f0ff530e1bd6 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
@@ -26,6 +26,7 @@ import java.time.Instant;
 import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -133,6 +134,7 @@ public class CamelMonitor extends CamelCommand {
 
     // Route sort columns
     private static final String[] ROUTE_SORT_COLUMNS = { "name", "group", 
"from", "status", "total", "failed" };
+    private static final String[] ROUTE_TOP_SORT_COLUMNS = { "mean", "max", 
"min", "last", "delta" };
 
     // Consumer sort columns (order matches table column order)
     private static final String[] CONSUMER_SORT_COLUMNS = { "id", "status", 
"type", "inflight", "total", "uri" };
@@ -214,6 +216,10 @@ public class CamelMonitor extends CamelCommand {
     private String routeSort = "name";
     private int routeSortIndex = 0;
     private boolean routeSortReversed;
+    private boolean routeTopMode;
+    private String routeTopSort = "mean";
+    private int routeTopSortIndex = 0;
+    private boolean routeTopSortReversed;
 
     // Consumer sort state (default: id = index 0)
     private String consumerSort = "id";
@@ -312,8 +318,10 @@ public class CamelMonitor extends CamelCommand {
     private int chartMode = CHART_ALL;
     private boolean showDiagram;
     private boolean diagramTextMode;
+    private boolean diagramAllRoutes;
     private boolean diagramMetrics = true;
     private List<RouteDiagramAsciiRenderer.CounterPos> diagramCounterPositions 
= Collections.emptyList();
+    private Set<Integer> diagramRouteTitleRows = Collections.emptySet();
     private List<String> diagramLines = Collections.emptyList();
     private int diagramScroll;
     private int diagramScrollX;
@@ -454,7 +462,15 @@ public class CamelMonitor extends CamelCommand {
                 return handleTabKey(TAB_CIRCUIT_BREAKER);
             }
 
-            // Tab cycling
+            // Tab cycling (check Shift+Tab before Tab since Tab binding also 
matches Shift+Tab)
+            if (ke.isFocusPrevious()) {
+                int prev = (tabsState.selected() - 1 + NUM_TABS) % NUM_TABS;
+                if (prev != TAB_OVERVIEW) {
+                    selectCurrentIntegration();
+                }
+                tabsState.select(prev);
+                return true;
+            }
             if (ke.isFocusNext()) {
                 int next = (tabsState.selected() + 1) % NUM_TABS;
                 if (next != TAB_OVERVIEW) {
@@ -692,13 +708,31 @@ public class CamelMonitor extends CamelCommand {
 
             // Routes tab: sort and diagram
             if (tab == TAB_ROUTES && ke.isChar('s')) {
-                routeSortIndex = (routeSortIndex + 1) % 
ROUTE_SORT_COLUMNS.length;
-                routeSort = ROUTE_SORT_COLUMNS[routeSortIndex];
-                routeSortReversed = false;
+                if (routeTopMode) {
+                    routeTopSortIndex = (routeTopSortIndex + 1) % 
ROUTE_TOP_SORT_COLUMNS.length;
+                    routeTopSort = ROUTE_TOP_SORT_COLUMNS[routeTopSortIndex];
+                    routeTopSortReversed = false;
+                } else {
+                    routeSortIndex = (routeSortIndex + 1) % 
ROUTE_SORT_COLUMNS.length;
+                    routeSort = ROUTE_SORT_COLUMNS[routeSortIndex];
+                    routeSortReversed = false;
+                }
                 return true;
             }
             if (tab == TAB_ROUTES && ke.isChar('S')) {
-                routeSortReversed = !routeSortReversed;
+                if (routeTopMode) {
+                    routeTopSortReversed = !routeTopSortReversed;
+                } else {
+                    routeSortReversed = !routeSortReversed;
+                }
+                return true;
+            }
+            if (tab == TAB_ROUTES && !showSource && !showDiagram && 
ke.isCharIgnoreCase('t')) {
+                routeTopMode = !routeTopMode;
+                return true;
+            }
+            if (tab == TAB_ROUTES && !showSource && !showDiagram && 
ke.isCharIgnoreCase('a')) {
+                diagramAllRoutes = !diagramAllRoutes;
                 return true;
             }
             if (tab == TAB_ROUTES && ke.isChar('d')) {
@@ -1191,7 +1225,7 @@ public class CamelMonitor extends CamelCommand {
         Line[] labels = {
                 Line.from(" 1 Overview "),
                 Line.from(" 2 Log "),
-                Line.from(" 3 Route "),
+                Line.from(routeTopMode ? " 3  Top  " : " 3 Route "),
                 Line.from(" 4 Consumer "),
                 Line.from(" 5 Endpoint "),
                 Line.from(" 6 HTTP "),
@@ -1683,12 +1717,16 @@ public class CamelMonitor extends CamelCommand {
 
         // Fullscreen diagram mode
         if (showDiagram && (diagramTextMode ? !diagramLines.isEmpty() : 
diagramFullImageData != null)) {
-            // Split: route info header (4 rows) + diagram (fill)
-            List<Rect> fullChunks = Layout.vertical()
-                    .constraints(Constraint.length(4), Constraint.fill())
-                    .split(area);
-            renderRouteHeader(frame, fullChunks.get(0), info);
-            renderDiagram(frame, fullChunks.get(1));
+            if (diagramAllRoutes) {
+                renderDiagram(frame, area);
+            } else {
+                // Split: route info header (4 rows) + diagram (fill)
+                List<Rect> fullChunks = Layout.vertical()
+                        .constraints(Constraint.length(4), Constraint.fill())
+                        .split(area);
+                renderRouteHeader(frame, fullChunks.get(0), info);
+                renderDiagram(frame, fullChunks.get(1));
+            }
             return;
         }
 
@@ -1702,69 +1740,130 @@ public class CamelMonitor extends CamelCommand {
                 .split(area);
 
         // Routes table
-        List<Row> routeRows = new ArrayList<>();
-        for (RouteInfo route : sortedRoutes) {
-            Style stateStyle = "Started".equals(route.state)
-                    ? Style.EMPTY.fg(Color.GREEN)
-                    : Style.EMPTY.fg(Color.LIGHT_RED);
-
-            Style failStyle = route.failed > 0
-                    ? Style.EMPTY.fg(Color.LIGHT_RED).bold()
-                    : Style.EMPTY;
-
-            String sinceLastRoute = formatSinceLastRoute(route);
-
-            routeRows.add(Row.from(
-                    Cell.from(Span.styled(route.routeId != null ? 
route.routeId : "", Style.EMPTY.fg(Color.CYAN))),
-                    Cell.from(Span.styled(route.group != null ? route.group : 
"", Style.EMPTY.dim())),
-                    Cell.from(route.from != null ? route.from : ""),
-                    Cell.from(Span.styled(route.state != null ? route.state : 
"", stateStyle)),
-                    Cell.from(route.uptime != null ? route.uptime : ""),
-                    rightCell(route.coverage != null ? route.coverage : "", 6),
-                    rightCell(route.throughput != null ? route.throughput : 
"", 8),
-                    rightCell(String.valueOf(route.total), 8),
-                    rightCell(String.valueOf(route.failed), 6, failStyle),
-                    rightCell(String.valueOf(route.inflight), 8),
-                    rightCell(route.total > 0
-                            ? route.minTime + "/" + route.maxTime + "/" + 
route.meanTime
-                            : "", 14),
-                    Cell.from(sinceLastRoute)));
+        Table routeTable;
+        if (routeTopMode) {
+            sortedRoutes.sort(this::sortRouteTop);
+
+            List<Row> routeRows = new ArrayList<>();
+            for (RouteInfo route : sortedRoutes) {
+                Style failStyle = route.failed > 0
+                        ? Style.EMPTY.fg(Color.LIGHT_RED).bold()
+                        : Style.EMPTY;
+
+                routeRows.add(Row.from(
+                        Cell.from(Span.styled(route.routeId != null ? 
route.routeId : "", Style.EMPTY.fg(Color.CYAN))),
+                        Cell.from(route.from != null ? route.from : ""),
+                        rightCell(route.total > 0 ? 
String.valueOf(route.meanTime) : "", 6, topTimeStyle(route.meanTime)),
+                        rightCell(route.total > 0 ? 
String.valueOf(route.maxTime) : "", 6, topTimeStyle(route.maxTime)),
+                        rightCell(route.total > 0 ? 
String.valueOf(route.minTime) : "", 6),
+                        rightCell(route.total > 0 ? 
String.valueOf(route.lastTime) : "", 6),
+                        rightCell(route.deltaTime != 0 ? 
String.valueOf(route.deltaTime) : "", 6,
+                                topDeltaStyle(route.deltaTime)),
+                        rightCell(String.valueOf(route.total), 8),
+                        rightCell(String.valueOf(route.failed), 6, failStyle),
+                        rightCell(String.valueOf(route.inflight), 8),
+                        rightCell(route.throughput != null ? route.throughput 
: "", 8),
+                        rightCell(formatLoad(route.load01, route.load05, 
route.load15), 12)));
+            }
+
+            routeTable = Table.builder()
+                    .rows(routeRows)
+                    .header(Row.from(
+                            Cell.from(Span.styled("ROUTE", 
Style.EMPTY.bold())),
+                            Cell.from(Span.styled("FROM", Style.EMPTY.bold())),
+                            rightCell(routeTopSortLabel("MEAN", "mean"), 6, 
routeTopSortStyle("mean")),
+                            rightCell(routeTopSortLabel("MAX", "max"), 6, 
routeTopSortStyle("max")),
+                            rightCell(routeTopSortLabel("MIN", "min"), 6, 
routeTopSortStyle("min")),
+                            rightCell(routeTopSortLabel("LAST", "last"), 6, 
routeTopSortStyle("last")),
+                            rightCell(routeTopSortLabel("DELTA", "delta"), 6, 
routeTopSortStyle("delta")),
+                            rightCell("TOTAL", 8, Style.EMPTY.bold()),
+                            rightCell("FAIL", 6, Style.EMPTY.bold()),
+                            rightCell("INFLIGHT", 8, Style.EMPTY.bold()),
+                            rightCell("MSG/S", 8, Style.EMPTY.bold()),
+                            rightCell("LOAD", 12, Style.EMPTY.bold())))
+                    .widths(
+                            Constraint.length(12),
+                            Constraint.fill(),
+                            Constraint.length(6),
+                            Constraint.length(6),
+                            Constraint.length(6),
+                            Constraint.length(6),
+                            Constraint.length(6),
+                            Constraint.length(8),
+                            Constraint.length(6),
+                            Constraint.length(8),
+                            Constraint.length(8),
+                            Constraint.length(12))
+                    
.highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
+                    .highlightSpacing(Table.HighlightSpacing.ALWAYS)
+                    .block(Block.builder().borderType(BorderType.ROUNDED)
+                            .title(" Top Routes sort:" + routeTopSort + " 
").build())
+                    .build();
+        } else {
+            List<Row> routeRows = new ArrayList<>();
+            for (RouteInfo route : sortedRoutes) {
+                Style stateStyle = "Started".equals(route.state)
+                        ? Style.EMPTY.fg(Color.GREEN)
+                        : Style.EMPTY.fg(Color.LIGHT_RED);
+
+                Style failStyle = route.failed > 0
+                        ? Style.EMPTY.fg(Color.LIGHT_RED).bold()
+                        : Style.EMPTY;
+
+                String sinceLastRoute = formatSinceLastRoute(route);
+
+                routeRows.add(Row.from(
+                        Cell.from(Span.styled(route.routeId != null ? 
route.routeId : "", Style.EMPTY.fg(Color.CYAN))),
+                        Cell.from(Span.styled(route.group != null ? 
route.group : "", Style.EMPTY.dim())),
+                        Cell.from(route.from != null ? route.from : ""),
+                        Cell.from(Span.styled(route.state != null ? 
route.state : "", stateStyle)),
+                        Cell.from(route.uptime != null ? route.uptime : ""),
+                        rightCell(route.coverage != null ? route.coverage : 
"", 6),
+                        rightCell(route.throughput != null ? route.throughput 
: "", 8),
+                        rightCell(String.valueOf(route.total), 8),
+                        rightCell(String.valueOf(route.failed), 6, failStyle),
+                        rightCell(String.valueOf(route.inflight), 8),
+                        rightCell(route.total > 0
+                                ? route.minTime + "/" + route.maxTime + "/" + 
route.meanTime
+                                : "", 14),
+                        Cell.from(sinceLastRoute)));
+            }
+
+            routeTable = Table.builder()
+                    .rows(routeRows)
+                    .header(Row.from(
+                            Cell.from(Span.styled(routeSortLabel("ROUTE", 
"name"), routeSortStyle("name"))),
+                            Cell.from(Span.styled(routeSortLabel("GROUP", 
"group"), routeSortStyle("group"))),
+                            Cell.from(Span.styled(routeSortLabel("FROM", 
"from"), routeSortStyle("from"))),
+                            Cell.from(Span.styled(routeSortLabel("STATUS", 
"status"), routeSortStyle("status"))),
+                            Cell.from(Span.styled("AGE", Style.EMPTY.bold())),
+                            rightCell("COVER", 6, Style.EMPTY.bold()),
+                            rightCell("MSG/S", 8, Style.EMPTY.bold()),
+                            rightCell(routeSortLabel("TOTAL", "total"), 8, 
routeSortStyle("total")),
+                            rightCell(routeSortLabel("FAIL", "failed"), 6, 
routeSortStyle("failed")),
+                            rightCell("INFLIGHT", 8, Style.EMPTY.bold()),
+                            rightCell("MIN/MAX/MEAN", 14, Style.EMPTY.bold()),
+                            Cell.from(Span.styled("SINCE-LAST", 
Style.EMPTY.bold()))))
+                    .widths(
+                            Constraint.length(12),
+                            Constraint.length(14),
+                            Constraint.fill(),
+                            Constraint.length(10),
+                            Constraint.length(8),
+                            Constraint.length(6),
+                            Constraint.length(8),
+                            Constraint.length(8),
+                            Constraint.length(6),
+                            Constraint.length(8),
+                            Constraint.length(14),
+                            Constraint.length(12))
+                    
.highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
+                    .highlightSpacing(Table.HighlightSpacing.ALWAYS)
+                    .block(Block.builder().borderType(BorderType.ROUNDED)
+                            .title(" Routes sort:" + routeSort + " ").build())
+                    .build();
         }
 
-        Table routeTable = Table.builder()
-                .rows(routeRows)
-                .header(Row.from(
-                        Cell.from(Span.styled(routeSortLabel("ROUTE", "name"), 
routeSortStyle("name"))),
-                        Cell.from(Span.styled(routeSortLabel("GROUP", 
"group"), routeSortStyle("group"))),
-                        Cell.from(Span.styled(routeSortLabel("FROM", "from"), 
routeSortStyle("from"))),
-                        Cell.from(Span.styled(routeSortLabel("STATUS", 
"status"), routeSortStyle("status"))),
-                        Cell.from(Span.styled("AGE", Style.EMPTY.bold())),
-                        rightCell("COVER", 6, Style.EMPTY.bold()),
-                        rightCell("MSG/S", 8, Style.EMPTY.bold()),
-                        rightCell(routeSortLabel("TOTAL", "total"), 8, 
routeSortStyle("total")),
-                        rightCell(routeSortLabel("FAIL", "failed"), 6, 
routeSortStyle("failed")),
-                        rightCell("INFLIGHT", 8, Style.EMPTY.bold()),
-                        rightCell("MIN/MAX/MEAN", 14, Style.EMPTY.bold()),
-                        Cell.from(Span.styled("SINCE-LAST", 
Style.EMPTY.bold()))))
-                .widths(
-                        Constraint.length(12),
-                        Constraint.length(14),
-                        Constraint.fill(),
-                        Constraint.length(10),
-                        Constraint.length(8),
-                        Constraint.length(6),
-                        Constraint.length(8),
-                        Constraint.length(8),
-                        Constraint.length(6),
-                        Constraint.length(8),
-                        Constraint.length(14),
-                        Constraint.length(12))
-                .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
-                .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                .block(Block.builder().borderType(BorderType.ROUNDED)
-                        .title(" Routes sort:" + routeSort + " ").build())
-                .build();
-
         frame.renderStatefulWidget(routeTable, chunks.get(0), routeTableState);
 
         // Bottom panel: diagram or processors
@@ -1853,6 +1952,63 @@ public class CamelMonitor extends CamelCommand {
         return routeSortReversed ? -result : result;
     }
 
+    private int sortRouteTop(RouteInfo a, RouteInfo b) {
+        int result = switch (routeTopSort) {
+            case "mean" -> Long.compare(b.meanTime, a.meanTime);
+            case "max" -> Long.compare(b.maxTime, a.maxTime);
+            case "min" -> Long.compare(b.minTime, a.minTime);
+            case "last" -> Long.compare(b.lastTime, a.lastTime);
+            case "delta" -> Long.compare(b.deltaTime, a.deltaTime);
+            default -> 0;
+        };
+        return routeTopSortReversed ? -result : result;
+    }
+
+    private int sortProcessorTop(ProcessorInfo a, ProcessorInfo b) {
+        int result = switch (routeTopSort) {
+            case "mean" -> Long.compare(b.meanTime, a.meanTime);
+            case "max" -> Long.compare(b.maxTime, a.maxTime);
+            case "min" -> Long.compare(b.minTime, a.minTime);
+            case "last" -> Long.compare(b.lastTime, a.lastTime);
+            case "delta" -> Long.compare(b.deltaTime, a.deltaTime);
+            default -> 0;
+        };
+        return routeTopSortReversed ? -result : result;
+    }
+
+    private String routeTopSortLabel(String label, String column) {
+        return sortLabel(label, column, routeTopSort, routeTopSortReversed);
+    }
+
+    private Style routeTopSortStyle(String column) {
+        return sortStyle(column, routeTopSort);
+    }
+
+    private static Style topTimeStyle(long ms) {
+        if (ms >= 1000) {
+            return Style.EMPTY.fg(Color.LIGHT_RED).bold();
+        } else if (ms >= 100) {
+            return Style.EMPTY.fg(Color.YELLOW);
+        }
+        return Style.EMPTY;
+    }
+
+    private static Style topDeltaStyle(long delta) {
+        if (delta > 0) {
+            return Style.EMPTY.fg(Color.LIGHT_RED);
+        } else if (delta < 0) {
+            return Style.EMPTY.fg(Color.GREEN);
+        }
+        return Style.EMPTY;
+    }
+
+    private static String formatLoad(String l1, String l5, String l15) {
+        String s1 = l1 != null && !"0.00".equals(l1) ? l1 : "0";
+        String s5 = l5 != null && !"0.00".equals(l5) ? l5 : "0";
+        String s15 = l15 != null && !"0.00".equals(l15) ? l15 : "0";
+        return s1 + "/" + s5 + "/" + s15;
+    }
+
     private String traceSortLabel(String label, String column) {
         return sortLabel(label, column, traceSort, traceSortReversed);
     }
@@ -2076,71 +2232,166 @@ public class CamelMonitor extends CamelCommand {
     }
 
     private void renderProcessors(Frame frame, Rect area, RouteInfo route) {
-        List<Row> rows = new ArrayList<>();
+        Table table;
+
+        if (routeTopMode) {
+            List<Row> rows = new ArrayList<>();
+
+            List<ProcessorInfo> sorted = new ArrayList<>(route.processors);
+            sorted.sort(this::sortProcessorTop);
+
+            long maxValue = 
sorted.stream().mapToLong(this::procChartValue).max().orElse(1);
+            if (maxValue <= 0) {
+                maxValue = 1;
+            }
 
-        // Synthetic top row representing the route itself
-        Style routeStyle = route.failed > 0 ? Style.EMPTY.fg(Color.LIGHT_RED) 
: Style.EMPTY.fg(Color.CYAN);
-        rows.add(Row.from(
-                Cell.from("   route"),
-                Cell.from(Span.styled(route.from != null ? route.from : 
route.routeId, routeStyle)),
-                Cell.from(""), Cell.from(""), Cell.from(""), Cell.from(""),
-                rightCell(String.valueOf(route.total), 8),
-                rightCell(String.valueOf(route.failed), 6,
-                        route.failed > 0 ? Style.EMPTY.fg(Color.LIGHT_RED) : 
Style.EMPTY),
-                rightCell(String.valueOf(route.inflight), 8),
-                rightCell(route.total > 0
-                        ? route.minTime + "/" + route.maxTime + "/" + 
route.meanTime
-                        : "", 14),
-                Cell.from("")));
-
-        for (ProcessorInfo proc : route.processors) {
-            String indent = "  ".repeat(proc.level);
-            Style nameStyle = proc.failed > 0 ? 
Style.EMPTY.fg(Color.LIGHT_RED) : Style.EMPTY.fg(Color.CYAN);
+            for (ProcessorInfo proc : sorted) {
+                Style nameStyle = proc.failed > 0 ? 
Style.EMPTY.fg(Color.LIGHT_RED) : Style.EMPTY.fg(Color.CYAN);
+                long chartVal = procChartValue(proc);
+                String bar;
+                if (chartVal > 0) {
+                    bar = buildBar(chartVal, maxValue, 20);
+                } else if (proc.total > 0) {
+                    bar = "█";
+                } else {
+                    bar = "";
+                }
+                Style barStyle = topTimeStyle(chartVal);
+                if (barStyle == Style.EMPTY) {
+                    barStyle = Style.EMPTY.fg(Color.CYAN);
+                }
+
+                rows.add(Row.from(
+                        Cell.from("   " + (proc.processor != null ? 
proc.processor : "")),
+                        Cell.from(Span.styled(proc.id != null ? proc.id : "", 
nameStyle)),
+                        Cell.from(Span.styled(bar, barStyle)),
+                        rightCell(proc.total > 0 ? 
String.valueOf(proc.meanTime) : "", 6, topTimeStyle(proc.meanTime)),
+                        rightCell(proc.total > 0 ? 
String.valueOf(proc.maxTime) : "", 6, topTimeStyle(proc.maxTime)),
+                        rightCell(proc.total > 0 ? 
String.valueOf(proc.minTime) : "", 6),
+                        rightCell(proc.total > 0 ? 
String.valueOf(proc.lastTime) : "", 6),
+                        rightCell(proc.deltaTime != 0 ? 
String.valueOf(proc.deltaTime) : "", 6, topDeltaStyle(proc.deltaTime)),
+                        rightCell(String.valueOf(proc.total), 8),
+                        rightCell(String.valueOf(proc.failed), 6,
+                                proc.failed > 0 ? 
Style.EMPTY.fg(Color.LIGHT_RED) : Style.EMPTY),
+                        rightCell(String.valueOf(proc.inflight), 8)));
+            }
+
+            table = Table.builder()
+                    .rows(rows)
+                    .header(Row.from(
+                            Cell.from(Span.styled("   TYPE", 
Style.EMPTY.bold())),
+                            Cell.from(Span.styled("PROCESSOR", 
Style.EMPTY.bold())),
+                            Cell.from(""),
+                            rightCell(routeTopSortLabel("MEAN", "mean"), 6, 
routeTopSortStyle("mean")),
+                            rightCell(routeTopSortLabel("MAX", "max"), 6, 
routeTopSortStyle("max")),
+                            rightCell(routeTopSortLabel("MIN", "min"), 6, 
routeTopSortStyle("min")),
+                            rightCell(routeTopSortLabel("LAST", "last"), 6, 
routeTopSortStyle("last")),
+                            rightCell(routeTopSortLabel("DELTA", "delta"), 6, 
routeTopSortStyle("delta")),
+                            rightCell("TOTAL", 8, Style.EMPTY.bold()),
+                            rightCell("FAIL", 6, Style.EMPTY.bold()),
+                            rightCell("INFLIGHT", 8, Style.EMPTY.bold())))
+                    .widths(
+                            Constraint.length(20),
+                            Constraint.length(14),
+                            Constraint.fill(),
+                            Constraint.length(6),
+                            Constraint.length(6),
+                            Constraint.length(6),
+                            Constraint.length(6),
+                            Constraint.length(6),
+                            Constraint.length(8),
+                            Constraint.length(6),
+                            Constraint.length(8))
+                    .block(Block.builder().borderType(BorderType.ROUNDED)
+                            .title(" Top Processors [" + route.routeId + "] 
sort:" + routeTopSort + " ").build())
+                    .build();
+        } else {
+            List<Row> rows = new ArrayList<>();
 
+            // Synthetic top row representing the route itself
+            Style routeStyle = route.failed > 0 ? 
Style.EMPTY.fg(Color.LIGHT_RED) : Style.EMPTY.fg(Color.CYAN);
             rows.add(Row.from(
-                    Cell.from("   " + (proc.processor != null ? proc.processor 
: "")),
-                    Cell.from(Span.styled(indent + (proc.id != null ? proc.id 
: ""), nameStyle)),
+                    Cell.from("   route"),
+                    Cell.from(Span.styled(route.from != null ? route.from : 
route.routeId, routeStyle)),
                     Cell.from(""), Cell.from(""), Cell.from(""), Cell.from(""),
-                    rightCell(String.valueOf(proc.total), 8),
-                    rightCell(String.valueOf(proc.failed), 6,
-                            proc.failed > 0 ? Style.EMPTY.fg(Color.LIGHT_RED) 
: Style.EMPTY),
-                    rightCell(String.valueOf(proc.inflight), 8),
-                    rightCell(proc.total > 0
-                            ? proc.minTime + "/" + proc.maxTime + "/" + 
proc.meanTime
+                    rightCell(String.valueOf(route.total), 8),
+                    rightCell(String.valueOf(route.failed), 6,
+                            route.failed > 0 ? Style.EMPTY.fg(Color.LIGHT_RED) 
: Style.EMPTY),
+                    rightCell(String.valueOf(route.inflight), 8),
+                    rightCell(route.total > 0
+                            ? route.minTime + "/" + route.maxTime + "/" + 
route.meanTime
                             : "", 14),
                     Cell.from("")));
-        }
 
-        Table table = Table.builder()
-                .rows(rows)
-                .header(Row.from(
-                        Cell.from(Span.styled("   TYPE", Style.EMPTY.bold())),
-                        Cell.from(Span.styled("PROCESSOR", 
Style.EMPTY.bold())),
+            for (ProcessorInfo proc : route.processors) {
+                String indent = "  ".repeat(proc.level);
+                Style nameStyle = proc.failed > 0 ? 
Style.EMPTY.fg(Color.LIGHT_RED) : Style.EMPTY.fg(Color.CYAN);
+
+                rows.add(Row.from(
+                        Cell.from("   " + (proc.processor != null ? 
proc.processor : "")),
+                        Cell.from(Span.styled(indent + (proc.id != null ? 
proc.id : ""), nameStyle)),
                         Cell.from(""), Cell.from(""), Cell.from(""), 
Cell.from(""),
-                        rightCell("TOTAL", 8, Style.EMPTY.bold()),
-                        rightCell("FAIL", 6, Style.EMPTY.bold()),
-                        rightCell("INFLIGHT", 8, Style.EMPTY.bold()),
-                        rightCell("MIN/MAX/MEAN", 14, Style.EMPTY.bold()),
-                        Cell.from("")))
-                .widths(
-                        Constraint.length(20),
-                        Constraint.fill(),
-                        Constraint.length(10),
-                        Constraint.length(8),
-                        Constraint.length(6),
-                        Constraint.length(8),
-                        Constraint.length(8),
-                        Constraint.length(6),
-                        Constraint.length(8),
-                        Constraint.length(14),
-                        Constraint.length(12))
-                .block(Block.builder().borderType(BorderType.ROUNDED)
-                        .title(" Processors [" + route.routeId + "] ").build())
-                .build();
+                        rightCell(String.valueOf(proc.total), 8),
+                        rightCell(String.valueOf(proc.failed), 6,
+                                proc.failed > 0 ? 
Style.EMPTY.fg(Color.LIGHT_RED) : Style.EMPTY),
+                        rightCell(String.valueOf(proc.inflight), 8),
+                        rightCell(proc.total > 0
+                                ? proc.minTime + "/" + proc.maxTime + "/" + 
proc.meanTime
+                                : "", 14),
+                        Cell.from("")));
+            }
+
+            table = Table.builder()
+                    .rows(rows)
+                    .header(Row.from(
+                            Cell.from(Span.styled("   TYPE", 
Style.EMPTY.bold())),
+                            Cell.from(Span.styled("PROCESSOR", 
Style.EMPTY.bold())),
+                            Cell.from(""), Cell.from(""), Cell.from(""), 
Cell.from(""),
+                            rightCell("TOTAL", 8, Style.EMPTY.bold()),
+                            rightCell("FAIL", 6, Style.EMPTY.bold()),
+                            rightCell("INFLIGHT", 8, Style.EMPTY.bold()),
+                            rightCell("MIN/MAX/MEAN", 14, Style.EMPTY.bold()),
+                            Cell.from("")))
+                    .widths(
+                            Constraint.length(20),
+                            Constraint.fill(),
+                            Constraint.length(10),
+                            Constraint.length(8),
+                            Constraint.length(6),
+                            Constraint.length(8),
+                            Constraint.length(8),
+                            Constraint.length(6),
+                            Constraint.length(8),
+                            Constraint.length(14),
+                            Constraint.length(12))
+                    .block(Block.builder().borderType(BorderType.ROUNDED)
+                            .title(" Processors [" + route.routeId + "] 
").build())
+                    .build();
+        }
 
         frame.renderStatefulWidget(table, area, processorTableState);
     }
 
+    private static String buildBar(long value, long maxValue, int maxWidth) {
+        if (value <= 0 || maxValue <= 0) {
+            return "";
+        }
+        int len = (int) Math.round((double) value / maxValue * maxWidth);
+        len = Math.max(len > 0 ? 1 : 0, Math.min(len, maxWidth));
+        return "█".repeat(len);
+    }
+
+    private long procChartValue(ProcessorInfo proc) {
+        return switch (routeTopSort) {
+            case "mean" -> proc.meanTime;
+            case "max" -> proc.maxTime;
+            case "min" -> proc.minTime;
+            case "last" -> proc.lastTime;
+            case "delta" -> Math.abs(proc.deltaTime);
+            default -> proc.meanTime;
+        };
+    }
+
     private void renderRouteHeader(Frame frame, Rect area, IntegrationInfo 
info) {
         RouteInfo route = null;
         if (diagramRouteId != null) {
@@ -2356,6 +2607,10 @@ public class CamelMonitor extends CamelCommand {
     }
 
     private Line styleDiagramLine(String text, int row, int scrollX) {
+        if (diagramRouteTitleRows.contains(row)) {
+            return Line.from(Span.styled(text, 
Style.EMPTY.fg(Color.WHITE).bold()));
+        }
+
         // Build counter color ranges for this row
         List<int[]> counterRanges = new ArrayList<>();
         for (RouteDiagramAsciiRenderer.CounterPos cp : 
diagramCounterPositions) {
@@ -2486,11 +2741,11 @@ public class CamelMonitor extends CamelCommand {
         String pid = selectedPid;
         boolean textMode = diagramTextMode;
         boolean showMetrics = diagramMetrics;
-        String routeId = selectedRoute.routeId;
+        String routeId = diagramAllRoutes ? null : selectedRoute.routeId;
 
         boolean initialLoad = !showDiagram;
         if (initialLoad) {
-            diagramRouteId = routeId;
+            diagramRouteId = routeId != null ? routeId : "all";
             diagramLines = List.of("(Loading diagram...)");
             diagramImageData = null;
             diagramFullImageData = null;
@@ -2759,39 +3014,49 @@ public class CamelMonitor extends CamelCommand {
             RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine(
                     RouteDiagramLayoutEngine.DEFAULT_BOX_WIDTH, 
RouteDiagramLayoutEngine.DEFAULT_FONT_SIZE,
                     RouteDiagramLayoutEngine.NodeLabelMode.CODE);
-            List<RouteDiagramLayoutEngine.LayoutRoute> layoutRoutes = new 
ArrayList<>();
-            int currentY = RouteDiagramLayoutEngine.PADDING;
-            for (RouteDiagramLayoutEngine.RouteInfo r : diagramRoutes) {
-                RouteDiagramLayoutEngine.LayoutRoute lr = 
engine.layoutRoute(r, currentY);
-                layoutRoutes.add(lr);
-                currentY = lr.maxY + RouteDiagramLayoutEngine.V_GAP;
-            }
-            RouteDiagramAsciiRenderer asciiRenderer = new 
RouteDiagramAsciiRenderer(
-                    RouteDiagramLayoutEngine.DEFAULT_BOX_WIDTH * 
RouteDiagramLayoutEngine.SCALE, true, metrics);
-            String ascii = asciiRenderer.renderDiagram(layoutRoutes, currentY);
-            List<RouteDiagramAsciiRenderer.CounterPos> origPositions = 
asciiRenderer.getCounterPositions();
 
-            // Build result lines, remapping counter positions to account for 
removed empty lines
-            String[] rawLines = ascii.split("\n", -1);
             List<String> result = new ArrayList<>();
-            int[] rowMapping = new int[rawLines.length];
-            int newRow = 0;
-            for (int i = 0; i < rawLines.length; i++) {
-                if (!rawLines[i].isEmpty()) {
-                    rowMapping[i] = newRow++;
-                    result.add(rawLines[i]);
-                } else {
-                    rowMapping[i] = -1;
-                }
-            }
             List<RouteDiagramAsciiRenderer.CounterPos> positions = new 
ArrayList<>();
-            for (RouteDiagramAsciiRenderer.CounterPos cp : origPositions) {
-                if (cp.row() >= 0 && cp.row() < rowMapping.length && 
rowMapping[cp.row()] >= 0) {
-                    positions.add(new RouteDiagramAsciiRenderer.CounterPos(
-                            rowMapping[cp.row()], cp.col(), cp.length(), 
cp.type()));
+            Set<Integer> titleRows = new HashSet<>();
+
+            for (RouteDiagramLayoutEngine.RouteInfo r : diagramRoutes) {
+                // Add separator between routes
+                if (!result.isEmpty()) {
+                    result.add("");
+                    result.add("");
+                }
+
+                int titleRow = result.size();
+
+                RouteDiagramLayoutEngine.LayoutRoute lr = 
engine.layoutRoute(r, RouteDiagramLayoutEngine.PADDING);
+                RouteDiagramAsciiRenderer asciiRenderer = new 
RouteDiagramAsciiRenderer(
+                        RouteDiagramLayoutEngine.DEFAULT_BOX_WIDTH * 
RouteDiagramLayoutEngine.SCALE, true, metrics);
+                String ascii = asciiRenderer.renderDiagram(List.of(lr), 
lr.maxY + RouteDiagramLayoutEngine.V_GAP);
+                List<RouteDiagramAsciiRenderer.CounterPos> origPositions = 
asciiRenderer.getCounterPositions();
+
+                // Strip empty lines and remap counter positions
+                String[] rawLines = ascii.split("\n", -1);
+                int[] rowMapping = new int[rawLines.length];
+                int baseRow = result.size();
+                int newRow = baseRow;
+                for (int i = 0; i < rawLines.length; i++) {
+                    if (!rawLines[i].isEmpty()) {
+                        rowMapping[i] = newRow++;
+                        result.add(rawLines[i]);
+                    } else {
+                        rowMapping[i] = -1;
+                    }
+                }
+                for (RouteDiagramAsciiRenderer.CounterPos cp : origPositions) {
+                    if (cp.row() >= 0 && cp.row() < rowMapping.length && 
rowMapping[cp.row()] >= 0) {
+                        positions.add(new RouteDiagramAsciiRenderer.CounterPos(
+                                rowMapping[cp.row()], cp.col(), cp.length(), 
cp.type()));
+                    }
                 }
+                titleRows.add(titleRow);
             }
-            applyDiagramResult(routeId, result, null, null, null, positions);
+
+            applyDiagramResult(routeId, result, null, null, null, positions, 
titleRows);
         } else {
             TerminalImageCapabilities caps = 
TerminalImageCapabilities.detect();
             if (caps.supportsNativeImages()) {
@@ -2822,20 +3087,27 @@ public class CamelMonitor extends CamelCommand {
 
     private void applyDiagramResult(
             String routeId, List<String> lines, ImageData imageData, ImageData 
fullImageData, ImageProtocol protocol) {
-        applyDiagramResult(routeId, lines, imageData, fullImageData, protocol, 
Collections.emptyList());
+        applyDiagramResult(routeId, lines, imageData, fullImageData, protocol, 
Collections.emptyList(), Collections.emptySet());
     }
 
     private void applyDiagramResult(
             String routeId, List<String> lines, ImageData imageData, ImageData 
fullImageData, ImageProtocol protocol,
             List<RouteDiagramAsciiRenderer.CounterPos> positions) {
+        applyDiagramResult(routeId, lines, imageData, fullImageData, protocol, 
positions, Collections.emptySet());
+    }
+
+    private void applyDiagramResult(
+            String routeId, List<String> lines, ImageData imageData, ImageData 
fullImageData, ImageProtocol protocol,
+            List<RouteDiagramAsciiRenderer.CounterPos> positions, Set<Integer> 
titleRows) {
         if (runner == null) {
             return;
         }
         runner.runOnRenderThread(() -> {
             boolean wasShowing = showDiagram;
-            diagramRouteId = routeId;
+            diagramRouteId = routeId != null ? routeId : "all";
             diagramLines = lines;
             diagramCounterPositions = positions;
+            diagramRouteTitleRows = titleRows;
             diagramImageData = imageData;
             diagramFullImageData = fullImageData;
             diagramProtocol = protocol;
@@ -4848,23 +5120,27 @@ public class CamelMonitor extends CamelCommand {
             hint(spans, "Esc", "back");
             hint(spans, "\u2191\u2193", "navigate");
             hint(spans, "s", "sort");
-            hint(spans, "c", "source");
-            hint(spans, "d", "diagram");
-            hint(spans, "D", "text diagram");
-            String routeState = selectedRouteState();
-            boolean supSus = selectedRouteSupportsSuspension();
-            if ("Started".equals(routeState)) {
-                hint(spans, "p", "stop");
-                if (supSus) {
-                    hint(spans, "P", "suspend");
-                }
-            } else if ("Suspended".equals(routeState)) {
-                hint(spans, "p", "start");
-                if (supSus) {
-                    hint(spans, "P", "resume");
+            hint(spans, "t", routeTopMode ? "top [on]" : "top [off]");
+            if (!routeTopMode) {
+                hint(spans, "c", "source");
+                hint(spans, "d", "diagram");
+                hint(spans, "D", "text diagram");
+                hint(spans, "a", diagramAllRoutes ? "all [on]" : "all [off]");
+                String routeState = selectedRouteState();
+                boolean supSus = selectedRouteSupportsSuspension();
+                if ("Started".equals(routeState)) {
+                    hint(spans, "p", "stop");
+                    if (supSus) {
+                        hint(spans, "P", "suspend");
+                    }
+                } else if ("Suspended".equals(routeState)) {
+                    hint(spans, "p", "start");
+                    if (supSus) {
+                        hint(spans, "P", "resume");
+                    }
+                } else if (routeState != null) {
+                    hint(spans, "p", "start");
                 }
-            } else if (routeState != null) {
-                hint(spans, "p", "start");
             }
             hint(spans, "1-9", "tabs");
         } else if (tab == TAB_CONSUMERS) {
@@ -5814,6 +6090,11 @@ public class CamelMonitor extends CamelCommand {
                     ri.meanTime = Math.max(0, 
objToLong(rs.get("meanProcessingTime")));
                     ri.minTime = Math.max(0, 
objToLong(rs.get("minProcessingTime")));
                     ri.maxTime = Math.max(0, 
objToLong(rs.get("maxProcessingTime")));
+                    ri.lastTime = Math.max(0, 
objToLong(rs.get("lastProcessingTime")));
+                    ri.deltaTime = objToLong(rs.get("deltaProcessingTime"));
+                    ri.load01 = objToString(rs.get("load01"));
+                    ri.load05 = objToString(rs.get("load05"));
+                    ri.load15 = objToString(rs.get("load15"));
                     long tsStarted = 
objToLong(rs.get("lastCreatedExchangeTimestamp"));
                     if (tsStarted > 0) {
                         ri.sinceLastStarted = TimeUtils.printSince(tsStarted);
@@ -5846,6 +6127,7 @@ public class CamelMonitor extends CamelCommand {
                             pi.minTime = Math.max(0, 
objToLong(ps.get("minProcessingTime")));
                             pi.maxTime = Math.max(0, 
objToLong(ps.get("maxProcessingTime")));
                             pi.lastTime = 
objToLong(ps.get("lastProcessingTime"));
+                            pi.deltaTime = 
objToLong(ps.get("deltaProcessingTime"));
                             pi.inflight = 
objToLong(ps.get("exchangesInflight"));
                             long tsStarted = 
objToLong(ps.get("lastCreatedExchangeTimestamp"));
                             if (tsStarted > 0) {
@@ -6276,6 +6558,11 @@ public class CamelMonitor extends CamelCommand {
         long meanTime;
         long minTime;
         long maxTime;
+        long lastTime;
+        long deltaTime;
+        String load01;
+        String load05;
+        String load15;
         String sinceLastStarted;
         String sinceLastCompleted;
         String sinceLastFailed;
@@ -6292,6 +6579,7 @@ public class CamelMonitor extends CamelCommand {
         long minTime;
         long maxTime;
         long lastTime;
+        long deltaTime;
         long inflight;
         String sinceLastStarted;
         String sinceLastCompleted;


Reply via email to