This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch feature/CAMEL-23672-tui-diagram in repository https://gitbox.apache.org/repos/asf/camel.git
commit 75e83695fbbee780638cd7c9346b191d3b9798d4 Author: Claus Ibsen <[email protected]> AuthorDate: Thu Jun 4 09:22:33 2026 +0200 CAMEL-23672: camel-tui - Diagram info panel improvements - Show external indicator (external → / → external) instead of link indicator for remote endpoints - Dashed arrows to remote endpoint nodes - Stable info panel height to prevent flicker when scrolling - Remove conditional Enter hint from footer (keep in F1 help) - Add since-last success/fail timestamps to both info panels - Move coverage next to uptime/throughput in topology info - Show Topology[camelId] with yellow styled name in title - Rename EIP Info to Info Co-Authored-By: Claude Opus 4.6 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../jbang/core/commands/tui/DiagramSupport.java | 4 +- .../dsl/jbang/core/commands/tui/DiagramTab.java | 68 ++++++++++++++++++---- .../commands/tui/diagram/RouteDiagramWidget.java | 6 +- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java index 30d2de167198..0b9d6635a401 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java @@ -520,10 +520,10 @@ class DiagramSupport { return boxes; } - void renderNativeDiagram(Frame frame, Rect area, String title, boolean metrics) { + void renderNativeDiagram(Frame frame, Rect area, Line title, boolean metrics) { Block block = Block.builder() .borderType(BorderType.ROUNDED) - .title(title) + .title(Title.from(title)) .build(); frame.renderWidget(block, area); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java index 3cfa55e4fe7d..f48e7854eabe 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java @@ -34,6 +34,7 @@ import dev.tamboui.tui.event.KeyEvent; import dev.tamboui.widgets.block.Block; import dev.tamboui.widgets.block.BorderType; import dev.tamboui.widgets.paragraph.Paragraph; +import org.apache.camel.util.TimeUtils; import org.apache.camel.util.json.JsonObject; import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*; @@ -308,7 +309,15 @@ class DiagramTab implements MonitorTab { String selectedRouteId = topologyMode ? diagram.getSelectedRouteId() : drillDownRouteId; if (topologyMode && diagram.hasNativeLayout()) { - String title = " Topology "; + Line title; + if (info.name != null) { + title = Line.from( + Span.raw(" Topology ["), + Span.styled(info.name, Style.EMPTY.fg(Color.YELLOW).bold()), + Span.raw("] ")); + } else { + title = Line.from(Span.raw(" Topology ")); + } if (selectedRouteId != null && area.width() > 60) { int panelWidth = 30; List<Rect> hChunks = Layout.horizontal() @@ -381,6 +390,11 @@ class DiagramTab implements MonitorTab { lines.add(Line.from( Span.styled(" Throughput: ", Style.EMPTY.dim()), Span.raw(route.throughput != null ? route.throughput : ""))); + if (route.coverage != null) { + lines.add(Line.from( + Span.styled(" Coverage: ", Style.EMPTY.dim()), + Span.raw(route.coverage))); + } lines.add(Line.from(Span.raw(""))); int w = numWidth(route.total, route.failed, route.inflight); @@ -409,12 +423,23 @@ class DiagramTab implements MonitorTab { Span.raw(String.format("%" + tw + "d ms", route.minTime)))); } - if (route.coverage != null) { + if (route.sinceLastCompleted != null || route.sinceLastFailed != null) { lines.add(Line.from(Span.raw(""))); lines.add(Line.from( - Span.styled(" Coverage: ", Style.EMPTY.dim()), - Span.raw(route.coverage))); + Span.styled(" Since last:", Style.EMPTY.dim()))); + if (route.sinceLastCompleted != null) { + lines.add(Line.from( + Span.styled(" success: ", Style.EMPTY.dim()), + Span.raw(route.sinceLastCompleted))); + } + if (route.sinceLastFailed != null) { + lines.add(Line.from( + Span.styled(" fail: ", Style.EMPTY.dim()), + Span.styled(route.sinceLastFailed, + Style.EMPTY.fg(Color.LIGHT_RED)))); + } } + } else { // External endpoint — show topology node data var topoNode = diagram.getSelectedTopologyNode(); @@ -491,12 +516,18 @@ class DiagramTab implements MonitorTab { Span.raw(ln.id))); } + lines.add(Line.from(Span.raw(""))); String linkedRoute = diagram.findLinkedRouteId(drillDownRouteId); - if (linkedRoute != null) { - lines.add(Line.from(Span.raw(""))); + if (linkedRoute != null && diagram.getRouteLayout(linkedRoute) != null) { lines.add(Line.from( Span.styled(" ↵ ", Style.EMPTY.fg(Color.YELLOW).bold()), Span.styled(linkedRoute, Style.EMPTY.fg(Color.WHITE)))); + } else if (ln.treeNode != null && ln.treeNode.info.remote) { + String arrow = "from".equals(ln.type) ? " external → " : " → external"; + lines.add(Line.from( + Span.styled(arrow, Style.EMPTY.fg(Color.DARK_GRAY)))); + } else { + lines.add(Line.from(Span.raw(""))); } if (ln.treeNode != null && ln.treeNode.info.stat != null) { @@ -531,6 +562,26 @@ class DiagramTab implements MonitorTab { lines.add(Line.from( Span.styled(" Last: ", Style.EMPTY.dim()), Span.raw(String.format("%" + tw + "d ms", stat.lastProcessingTime)))); + + if (stat.lastCompletedExchangeTimestamp > 0 || stat.lastFailedExchangeTimestamp > 0) { + long now = System.currentTimeMillis(); + lines.add(Line.from(Span.raw(""))); + lines.add(Line.from( + Span.styled(" Since last:", Style.EMPTY.dim()))); + if (stat.lastCompletedExchangeTimestamp > 0) { + long ago = now - stat.lastCompletedExchangeTimestamp; + lines.add(Line.from( + Span.styled(" success: ", Style.EMPTY.dim()), + Span.raw(TimeUtils.printDuration(ago, true)))); + } + if (stat.lastFailedExchangeTimestamp > 0) { + long ago = now - stat.lastFailedExchangeTimestamp; + lines.add(Line.from( + Span.styled(" fail: ", Style.EMPTY.dim()), + Span.styled(TimeUtils.printDuration(ago, true), + Style.EMPTY.fg(Color.LIGHT_RED)))); + } + } } } } else { @@ -540,7 +591,7 @@ class DiagramTab implements MonitorTab { Paragraph paragraph = Paragraph.builder() .text(Text.from(lines)) .block(Block.builder().borderType(BorderType.ROUNDED) - .title(" EIP Info ").build()) + .title(" Info ").build()) .build(); frame.renderWidget(paragraph, area); } @@ -556,9 +607,6 @@ class DiagramTab implements MonitorTab { hint(spans, "Esc", "back"); hint(spans, "t", "topology"); hint(spans, "↑↓←→", "navigate"); - if (diagram.findLinkedRouteId(drillDownRouteId) != null) { - hint(spans, "Enter", "jump to route"); - } hint(spans, "PgUp/PgDn", "page"); hint(spans, "c", "source"); } else if (!topologyMode) { diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/RouteDiagramWidget.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/RouteDiagramWidget.java index 97e3cfc8367b..4b616eb51af8 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/RouteDiagramWidget.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/RouteDiagramWidget.java @@ -193,9 +193,7 @@ public class RouteDiagramWidget implements Widget { // Link indicator for nodes that connect to other routes String linkedRouteId = findLinkedRouteId(node); if (linkedRouteId != null) { - Style linkStyle = selected - ? Style.EMPTY.fg(Color.YELLOW).bold().patch(SELECTION_STYLE) - : Style.EMPTY.fg(Color.YELLOW).bold(); + Style linkStyle = Style.EMPTY.fg(Color.YELLOW).bold(); writeText(buffer, area, bottom, col + boxWidth, " ↵ " + linkedRouteId, linkStyle); } @@ -210,7 +208,7 @@ public class RouteDiagramWidget implements Widget { StatInfo stat = resolveStatInfo(to); long total = stat != null ? stat.exchangesTotal : 0; - boolean dashed = showMetrics && total == 0; + boolean dashed = (showMetrics && total == 0) || isExternalEndpoint(to); drawArrowPath(buffer, area, fromCx, fromBottom, toCx, toTop, dashed); drawCounters(buffer, area, toCx, toTop, stat);
