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 29c99a449ef9 CAMEL-23420: Add diagram modes and fullscreen scrolling
to TUI monitor (#23192)
29c99a449ef9 is described below
commit 29c99a449ef9f2f1e0eb9617828f136ae1a60c7e
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed May 13 15:46:35 2026 +0200
CAMEL-23420: Add diagram modes and fullscreen scrolling to TUI monitor
(#23192)
* CAMEL-23420: Add text diagram mode (Shift+D) to TUI monitor
Co-Authored-By: Claude Opus 4.7 <[email protected]>
* CAMEL-23420: Fullscreen text diagram with scrollbars in TUI monitor
Co-Authored-By: Claude Opus 4.7 <[email protected]>
* CAMEL-23420: Show matching close key (d/D) in diagram footer
Co-Authored-By: Claude Opus 4.7 <[email protected]>
* CAMEL-23420: Show live route info header in fullscreen text diagram
Co-Authored-By: Claude Opus 4.7 <[email protected]>
* CAMEL-23420: Hide diagram block title in fullscreen text mode
Co-Authored-By: Claude Opus 4.7 <[email protected]>
* CAMEL-23420: Fullscreen image diagram with scrollbars and flicker fix
Co-Authored-By: Claude Opus 4.7 <[email protected]>
* CAMEL-23420: Add Home/End keys for diagram top/bottom scrolling
Co-Authored-By: Claude Opus 4.7 <[email protected]>
---------
Co-authored-by: Claude Opus 4.7 <[email protected]>
---
.../dsl/jbang/core/commands/tui/CamelMonitor.java | 387 +++++++++++++++++----
1 file changed, 315 insertions(+), 72 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 7fcb1d958e2f..2c20cbb0a26f 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
@@ -66,6 +66,8 @@ import dev.tamboui.widgets.block.Block;
import dev.tamboui.widgets.block.BorderType;
import dev.tamboui.widgets.gauge.Gauge;
import dev.tamboui.widgets.paragraph.Paragraph;
+import dev.tamboui.widgets.scrollbar.Scrollbar;
+import dev.tamboui.widgets.scrollbar.ScrollbarState;
import dev.tamboui.widgets.table.Cell;
import dev.tamboui.widgets.table.Row;
import dev.tamboui.widgets.table.Table;
@@ -169,11 +171,20 @@ public class CamelMonitor extends CamelCommand {
// Diagram state
private boolean showDiagram;
+ private boolean diagramTextMode;
private List<String> diagramLines = Collections.emptyList();
private int diagramScroll;
+ private int diagramScrollX;
+ private final ScrollbarState diagramVScrollState = new ScrollbarState();
+ private final ScrollbarState diagramHScrollState = new ScrollbarState();
private String diagramRouteId;
private ImageData diagramImageData;
+ private ImageData diagramFullImageData;
private ImageProtocol diagramProtocol;
+ private int diagramCropX = -1;
+ private int diagramCropY = -1;
+ private int diagramCropW = -1;
+ private int diagramCropH = -1;
private volatile long lastRefresh;
@@ -210,13 +221,13 @@ public class CamelMonitor extends CamelCommand {
private boolean handleEvent(Event event, TuiRunner runner) {
if (event instanceof MouseEvent me) {
- if (showDiagram && diagramImageData == null &&
tabsState.selected() == TAB_ROUTES) {
+ if (showDiagram && tabsState.selected() == TAB_ROUTES) {
if (me.kind() == MouseEventKind.SCROLL_UP) {
diagramScroll = Math.max(0, diagramScroll - 3);
return true;
}
if (me.kind() == MouseEventKind.SCROLL_DOWN) {
- diagramScroll = Math.min(Math.max(0, diagramLines.size() -
1), diagramScroll + 3);
+ diagramScroll += 3;
return true;
}
}
@@ -227,6 +238,7 @@ public class CamelMonitor extends CamelCommand {
if (showDiagram) {
showDiagram = false;
diagramImageData = null;
+ diagramFullImageData = null;
return true;
}
// If in a detail tab, go back to overview first
@@ -278,7 +290,7 @@ public class CamelMonitor extends CamelCommand {
// Navigation (all tabs)
if (ke.isUp()) {
- if (showDiagram && diagramImageData == null && tab ==
TAB_ROUTES) {
+ if (showDiagram && tab == TAB_ROUTES) {
diagramScroll = Math.max(0, diagramScroll - 1);
} else {
navigateUp();
@@ -286,15 +298,15 @@ public class CamelMonitor extends CamelCommand {
return true;
}
if (ke.isDown()) {
- if (showDiagram && diagramImageData == null && tab ==
TAB_ROUTES) {
- diagramScroll = Math.min(Math.max(0, diagramLines.size() -
1), diagramScroll + 1);
+ if (showDiagram && tab == TAB_ROUTES) {
+ diagramScroll++;
} else {
navigateDown();
}
return true;
}
if (ke.isKey(KeyCode.PAGE_UP)) {
- if (showDiagram && diagramImageData == null && tab ==
TAB_ROUTES) {
+ if (showDiagram && tab == TAB_ROUTES) {
diagramScroll = Math.max(0, diagramScroll - 20);
} else if (tab == TAB_LOG) {
logFollowMode = false;
@@ -305,8 +317,8 @@ public class CamelMonitor extends CamelCommand {
return true;
}
if (ke.isKey(KeyCode.PAGE_DOWN)) {
- if (showDiagram && diagramImageData == null && tab ==
TAB_ROUTES) {
- diagramScroll = Math.min(Math.max(0, diagramLines.size() -
1), diagramScroll + 20);
+ if (showDiagram && tab == TAB_ROUTES) {
+ diagramScroll += 20;
} else if (tab == TAB_LOG) {
for (int i = 0; i < 20; i++) {
logTableState.selectNext(filteredLogEntries.size());
@@ -314,6 +326,31 @@ public class CamelMonitor extends CamelCommand {
}
return true;
}
+ if (ke.isLeft()) {
+ if (showDiagram && tab == TAB_ROUTES) {
+ diagramScrollX = Math.max(0, diagramScrollX - 1);
+ return true;
+ }
+ }
+ if (ke.isRight()) {
+ if (showDiagram && tab == TAB_ROUTES) {
+ diagramScrollX++;
+ return true;
+ }
+ }
+ if (ke.isKey(KeyCode.HOME)) {
+ if (showDiagram && tab == TAB_ROUTES) {
+ diagramScroll = 0;
+ diagramScrollX = 0;
+ return true;
+ }
+ }
+ if (ke.isKey(KeyCode.END)) {
+ if (showDiagram && tab == TAB_ROUTES) {
+ diagramScroll = Integer.MAX_VALUE;
+ return true;
+ }
+ }
// Enter to drill into selected integration
if (ke.isKey(KeyCode.ENTER) && tab == TAB_OVERVIEW) {
@@ -330,11 +367,24 @@ public class CamelMonitor extends CamelCommand {
routeSort = ROUTE_SORT_COLUMNS[routeSortIndex];
return true;
}
- if (tab == TAB_ROUTES && ke.isCharIgnoreCase('d')) {
+ if (tab == TAB_ROUTES && ke.isChar('d')) {
if (showDiagram) {
showDiagram = false;
diagramImageData = null;
+ diagramFullImageData = null;
} else {
+ diagramTextMode = false;
+ loadDiagramForSelectedRoute();
+ }
+ return true;
+ }
+ if (tab == TAB_ROUTES && ke.isChar('D')) {
+ if (showDiagram) {
+ showDiagram = false;
+ diagramImageData = null;
+ diagramFullImageData = null;
+ } else {
+ diagramTextMode = true;
loadDiagramForSelectedRoute();
}
return true;
@@ -412,10 +462,13 @@ public class CamelMonitor extends CamelCommand {
}
if (event instanceof TickEvent) {
long now = System.currentTimeMillis();
- if (now - lastRefresh >= refreshInterval) {
+ long interval = showDiagram ? Math.max(refreshInterval, 1000) :
refreshInterval;
+ if (now - lastRefresh >= interval) {
refreshData();
+ return true;
}
- return true;
+ // Skip re-render when showing image diagram to prevent flicker
+ return diagramFullImageData == null;
}
return false;
}
@@ -682,6 +735,17 @@ public class CamelMonitor extends CamelCommand {
return;
}
+ // 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));
+ return;
+ }
+
// Sort routes
List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes);
sortedRoutes.sort(this::sortRoute);
@@ -830,40 +894,222 @@ public class CamelMonitor extends CamelCommand {
frame.renderStatefulWidget(table, area, new TableState());
}
+ private void renderRouteHeader(Frame frame, Rect area, IntegrationInfo
info) {
+ RouteInfo route = null;
+ if (diagramRouteId != null) {
+ for (RouteInfo r : info.routes) {
+ if (diagramRouteId.equals(r.routeId)) {
+ route = r;
+ break;
+ }
+ }
+ }
+
+ List<Row> rows = new ArrayList<>();
+ if (route != null) {
+ Style stateStyle = "Started".equals(route.state)
+ ? Style.create().fg(Color.GREEN)
+ : Style.create().fg(Color.RED);
+ Style failStyle = route.failed > 0
+ ? Style.create().fg(Color.RED).bold()
+ : Style.create();
+ rows.add(Row.from(
+ Cell.from(Span.styled(route.routeId != null ?
route.routeId : "", Style.create().fg(Color.CYAN))),
+ Cell.from(route.from != null ? route.from : ""),
+ Cell.from(Span.styled(route.state != null ? route.state :
"", stateStyle)),
+ Cell.from(route.uptime != null ? route.uptime : ""),
+ Cell.from(route.throughput != null ? route.throughput :
""),
+ Cell.from(String.valueOf(route.total)),
+ Cell.from(Span.styled(String.valueOf(route.failed),
failStyle)),
+ Cell.from(route.meanTime + "/" + route.maxTime)));
+ }
+
+ Table table = Table.builder()
+ .rows(rows)
+ .header(Row.from(
+ Cell.from(Span.styled("ROUTE", Style.create().bold())),
+ Cell.from(Span.styled("FROM", Style.create().bold())),
+ Cell.from(Span.styled("STATE", Style.create().bold())),
+ Cell.from(Span.styled("UPTIME",
Style.create().bold())),
+ Cell.from(Span.styled("THRUPUT",
Style.create().bold())),
+ Cell.from(Span.styled("TOTAL", Style.create().bold())),
+ Cell.from(Span.styled("FAILED",
Style.create().bold())),
+ Cell.from(Span.styled("MEAN/MAX",
Style.create().bold()))))
+ .widths(
+ Constraint.length(12),
+ Constraint.fill(),
+ Constraint.length(10),
+ Constraint.length(8),
+ Constraint.length(10),
+ Constraint.length(8),
+ Constraint.length(8),
+ Constraint.length(12))
+ .highlightStyle(Style.create().fg(Color.WHITE).bold().onBlue())
+ .block(Block.builder().borderType(BorderType.ROUNDED)
+ .title(" Route [" + info.name + "] ").build())
+ .build();
+
+ frame.renderStatefulWidget(table, area, new TableState());
+ }
+
private void renderDiagram(Frame frame, Rect area) {
Block block = Block.builder()
.borderType(BorderType.ROUNDED)
- .title(" Diagram [" + diagramRouteId + "] ")
+ .title(diagramTextMode ? "" : " Diagram [" + diagramRouteId +
"] ")
.build();
- if (diagramImageData != null) {
- Image img = Image.builder()
- .data(diagramImageData)
- .protocol(diagramProtocol)
- .scaling(ImageScaling.FIT)
- .block(block)
- .build();
- frame.renderWidget(img, area);
+ if (diagramFullImageData != null) {
+ renderImageDiagram(frame, area, block);
return;
}
+ // Compute max width for horizontal scrolling
+ int maxWidth = 0;
+ for (String line : diagramLines) {
+ maxWidth = Math.max(maxWidth, line.length());
+ }
+
Rect inner = block.inner(area);
- int visibleLines = inner.height();
- int maxScroll = Math.max(0, diagramLines.size() - visibleLines);
- diagramScroll = Math.min(diagramScroll, maxScroll);
+ // Reserve 1 col for vertical scrollbar, 1 row for horizontal scrollbar
+ int visibleLines = Math.max(1, inner.height() - 1);
+ int visibleCols = Math.max(1, inner.width() - 1);
+ int maxVScroll = Math.max(0, diagramLines.size() - visibleLines);
+ int maxHScroll = Math.max(0, maxWidth - visibleCols);
+ diagramScroll = Math.min(diagramScroll, maxVScroll);
+ diagramScrollX = Math.min(diagramScrollX, maxHScroll);
+
+ // Build visible lines with horizontal offset applied
List<Line> lines = new ArrayList<>();
int end = Math.min(diagramScroll + visibleLines, diagramLines.size());
for (int i = diagramScroll; i < end; i++) {
- lines.add(styleDiagramLine(diagramLines.get(i)));
+ String line = diagramLines.get(i);
+ if (diagramScrollX > 0) {
+ line = diagramScrollX < line.length() ?
line.substring(diagramScrollX) : "";
+ }
+ lines.add(styleDiagramLine(line));
}
+ // Layout: outer block wraps everything, inner splits content +
scrollbars
+ frame.renderWidget(block, area);
+
+ // Vertical layout inside the block: [content row (fill), horizontal
scrollbar (1 row)]
+ List<Rect> vChunks = Layout.vertical()
+ .constraints(Constraint.fill(), Constraint.length(1))
+ .split(inner);
+
+ // Horizontal layout for content row: [text (fill), vertical scrollbar
(1 col)]
+ List<Rect> hChunks = Layout.horizontal()
+ .constraints(Constraint.fill(), Constraint.length(1))
+ .split(vChunks.get(0));
+
+ // Render diagram text
Paragraph paragraph = Paragraph.builder()
.text(Text.from(lines))
- .block(block)
.build();
+ frame.renderWidget(paragraph, hChunks.get(0));
+
+ // Render vertical scrollbar
+ diagramVScrollState.contentLength(diagramLines.size());
+ diagramVScrollState.viewportContentLength(visibleLines);
+ diagramVScrollState.position(diagramScroll);
+ frame.renderStatefulWidget(
+ Scrollbar.builder()
+ .thumbStyle(Style.create().fg(Color.rgb(0xF6, 0x91,
0x23)))
+ .build(),
+ hChunks.get(1), diagramVScrollState);
- frame.renderWidget(paragraph, area);
+ // Render horizontal scrollbar
+ if (maxWidth > visibleCols) {
+ diagramHScrollState.contentLength(maxWidth);
+ diagramHScrollState.viewportContentLength(visibleCols);
+ diagramHScrollState.position(diagramScrollX);
+ frame.renderStatefulWidget(
+ Scrollbar.horizontal(),
+ vChunks.get(1), diagramHScrollState);
+ }
+ }
+
+ private void renderImageDiagram(Frame frame, Rect area, Block block) {
+ int imgW = diagramFullImageData.width();
+ int imgH = diagramFullImageData.height();
+
+ Rect inner = block.inner(area);
+ // Convert cell area to pixel viewport using protocol resolution
+ int pxPerCol = diagramProtocol.resolution().widthMultiplier();
+ int pxPerRow = diagramProtocol.resolution().heightMultiplier();
+ // Reserve 1 col for vertical scrollbar, 1 row for horizontal scrollbar
+ int viewCols = Math.max(1, inner.width() - 1);
+ int viewRows = Math.max(1, inner.height() - 1);
+ int viewW = viewCols * pxPerCol;
+ int viewH = viewRows * pxPerRow;
+
+ // Scroll units are in cells; convert to pixels for clamping
+ int maxScrollY = Math.max(0, (imgH - viewH + pxPerRow - 1) / pxPerRow);
+ int maxScrollX = Math.max(0, (imgW - viewW + pxPerCol - 1) / pxPerCol);
+ diagramScroll = Math.min(diagramScroll, maxScrollY);
+ diagramScrollX = Math.min(diagramScrollX, maxScrollX);
+
+ int cropX = Math.min(diagramScrollX * pxPerCol, imgW);
+ int cropY = Math.min(diagramScroll * pxPerRow, imgH);
+ int cropW = Math.min(viewW, imgW - cropX);
+ int cropH = Math.min(viewH, imgH - cropY);
+
+ if (cropW > 0 && cropH > 0) {
+ if (cropX != diagramCropX || cropY != diagramCropY
+ || cropW != diagramCropW || cropH != diagramCropH) {
+ diagramImageData = diagramFullImageData.crop(cropX, cropY,
cropW, cropH);
+ diagramCropX = cropX;
+ diagramCropY = cropY;
+ diagramCropW = cropW;
+ diagramCropH = cropH;
+ }
+ } else if (diagramImageData != diagramFullImageData) {
+ diagramImageData = diagramFullImageData;
+ }
+
+ // Render the outer block border
+ frame.renderWidget(block, area);
+
+ // Vertical layout inside the block: [image+vscrollbar (fill),
hscrollbar (1 row)]
+ List<Rect> vChunks = Layout.vertical()
+ .constraints(Constraint.fill(), Constraint.length(1))
+ .split(inner);
+
+ // Horizontal layout: [image (fill), vertical scrollbar (1 col)]
+ List<Rect> hChunks = Layout.horizontal()
+ .constraints(Constraint.fill(), Constraint.length(1))
+ .split(vChunks.get(0));
+
+ // Render cropped image
+ Image img = Image.builder()
+ .data(diagramImageData)
+ .protocol(diagramProtocol)
+ .scaling(ImageScaling.FIT)
+ .build();
+ frame.renderWidget(img, hChunks.get(0));
+
+ // Render vertical scrollbar
+ int totalRows = (imgH + pxPerRow - 1) / pxPerRow;
+ diagramVScrollState.contentLength(totalRows);
+ diagramVScrollState.viewportContentLength(viewRows);
+ diagramVScrollState.position(diagramScroll);
+ frame.renderStatefulWidget(
+ Scrollbar.builder()
+ .thumbStyle(Style.create().fg(Color.rgb(0xF6, 0x91,
0x23)))
+ .build(),
+ hChunks.get(1), diagramVScrollState);
+
+ // Render horizontal scrollbar
+ if (imgW > viewW) {
+ int totalCols = (imgW + pxPerCol - 1) / pxPerCol;
+ diagramHScrollState.contentLength(totalCols);
+ diagramHScrollState.viewportContentLength(viewCols);
+ diagramHScrollState.position(diagramScrollX);
+ frame.renderStatefulWidget(
+ Scrollbar.horizontal(),
+ vChunks.get(1), diagramHScrollState);
+ }
}
private Line styleDiagramLine(String text) {
@@ -999,61 +1245,53 @@ public class CamelMonitor extends CamelCommand {
diagramRouteId = selectedRoute.routeId;
diagramScroll = 0;
+ diagramScrollX = 0;
+ diagramCropX = -1;
+ diagramCropY = -1;
+ diagramCropW = -1;
+ diagramCropH = -1;
- TerminalImageCapabilities caps = TerminalImageCapabilities.detect();
- if (caps.supportsNativeImages()) {
- RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine();
- List<RouteDiagramLayoutEngine.LayoutRoute> layoutRoutes = new
ArrayList<>();
- int totalHeight = 0;
- for (RouteDiagramLayoutEngine.RouteInfo r : diagramRoutes) {
- RouteDiagramLayoutEngine.LayoutRoute lr =
engine.layoutRoute(r, totalHeight);
- layoutRoutes.add(lr);
- totalHeight = lr.maxY;
- }
- RouteDiagramRenderer renderer = new RouteDiagramRenderer();
- RouteDiagramRenderer.DiagramColors colors =
RouteDiagramRenderer.DiagramColors.parse("transparent");
- java.awt.image.BufferedImage image =
renderer.renderDiagram(layoutRoutes, totalHeight, colors);
- diagramImageData = ImageData.fromBufferedImage(image);
- diagramProtocol = caps.bestProtocol();
- diagramLines = Collections.emptyList();
- } else {
+ if (diagramTextMode) {
diagramImageData = null;
+ diagramFullImageData = null;
diagramProtocol = null;
- StringBuilder sb = new StringBuilder();
- org.apache.camel.dsl.jbang.core.common.Printer capturingPrinter
- = new org.apache.camel.dsl.jbang.core.common.Printer() {
- @Override
- public void println() {
- sb.append('\n');
- }
-
- @Override
- public void println(String line) {
- sb.append(line).append('\n');
- }
-
- @Override
- public void print(String output) {
- sb.append(output);
- }
-
- @Override
- public void printf(String format, Object... args) {
- sb.append(String.format(format, args));
- }
- };
-
String ascii = renderAscii(diagramRoutes,
RouteDiagramLayoutEngine.DEFAULT_BOX_WIDTH, "CODE", true);
- sb.append(ascii);
List<String> result = new ArrayList<>();
- for (String line : sb.toString().split("\n", -1)) {
+ for (String line : ascii.split("\n", -1)) {
if (!line.isEmpty()) {
result.add(line);
}
}
diagramLines = result;
+ } else {
+ TerminalImageCapabilities caps =
TerminalImageCapabilities.detect();
+ if (caps.supportsNativeImages()) {
+ RouteDiagramLayoutEngine engine = new
RouteDiagramLayoutEngine();
+ List<RouteDiagramLayoutEngine.LayoutRoute> layoutRoutes = new
ArrayList<>();
+ int totalHeight = 0;
+ for (RouteDiagramLayoutEngine.RouteInfo r : diagramRoutes) {
+ RouteDiagramLayoutEngine.LayoutRoute lr =
engine.layoutRoute(r, totalHeight);
+ layoutRoutes.add(lr);
+ totalHeight = lr.maxY;
+ }
+ RouteDiagramRenderer renderer = new RouteDiagramRenderer();
+ RouteDiagramRenderer.DiagramColors colors =
RouteDiagramRenderer.DiagramColors.parse("transparent");
+ java.awt.image.BufferedImage image =
renderer.renderDiagram(layoutRoutes, totalHeight, colors);
+ ImageData fullImage = ImageData.fromBufferedImage(image);
+ diagramFullImageData = fullImage.resize(fullImage.width() / 2,
fullImage.height() / 2);
+ diagramImageData = diagramFullImageData;
+ diagramProtocol = caps.bestProtocol();
+ diagramLines = Collections.emptyList();
+ } else {
+ diagramImageData = null;
+ diagramFullImageData = null;
+ diagramProtocol = null;
+ diagramLines = List.of(
+ "(Terminal does not support image rendering)",
+ "(Press Shift+D for text diagram)");
+ }
}
showDiagram = true;
@@ -1680,15 +1918,18 @@ public class CamelMonitor extends CamelCommand {
Span.styled("Refresh: " + refreshLabel,
Style.create().dim()));
} else if (tab == TAB_ROUTES) {
if (showDiagram) {
+ String closeKey = diagramTextMode ? "D" : "d";
footer = Line.from(
- Span.styled(" d",
Style.create().fg(Color.YELLOW).bold()),
+ Span.styled(" " + closeKey,
Style.create().fg(Color.YELLOW).bold()),
Span.raw("/"),
Span.styled("Esc",
Style.create().fg(Color.YELLOW).bold()),
Span.raw(" close "),
- Span.styled("\u2191\u2193",
Style.create().fg(Color.YELLOW).bold()),
+ Span.styled("\u2191\u2193\u2190\u2192",
Style.create().fg(Color.YELLOW).bold()),
Span.raw(" scroll "),
Span.styled("PgUp/PgDn",
Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" page"));
+ Span.raw(" page "),
+ Span.styled("Home/End",
Style.create().fg(Color.YELLOW).bold()),
+ Span.raw(" top/bottom"));
} else {
footer = Line.from(
Span.styled(" Esc",
Style.create().fg(Color.YELLOW).bold()),
@@ -1699,6 +1940,8 @@ public class CamelMonitor extends CamelCommand {
Span.raw(" sort "),
Span.styled("d",
Style.create().fg(Color.YELLOW).bold()),
Span.raw(" diagram "),
+ Span.styled("D",
Style.create().fg(Color.YELLOW).bold()),
+ Span.raw(" text diagram "),
Span.styled("1-6",
Style.create().fg(Color.YELLOW).bold()),
Span.raw(" tabs "),
Span.styled("Refresh: " + refreshLabel,
Style.create().dim()));