This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/camel-tui-endpoint-2 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 32501077fbf75b6caa22b6d0191beb6caed60abb Author: Claus Ibsen <[email protected]> AuthorDate: Sun May 17 18:18:04 2026 +0200 TUI: endpoint tab UX improvements - remote filter, colors, layout - Endpoint flow panel and mirrored sparkline now respect the remote on/off toggle: maintain two parallel sliding window histories (all vs remote-only) and switch between them at render time; flow totals also filtered accordingly - Replace Color.BLUE with Color.CYAN throughout the endpoints tab (DIR out arrow, flow panel, chart legend, MirroredSparkline) and in the routes diagram (to/toD/wireTap/enrich/pollEnrich nodes) for visibility on dark terminals - Use BRIGHT_GREEN (AnsiColor) for in-traffic: throughput chart bars and legend, endpoint DIR in arrow, flow panel in-arrow and legend, MirroredSparkline top bars - Integration name box in flow diagram coloured yellow bold - Flow diagram in/out direction arrows and labels always shown in their direction colour regardless of traffic level; only count numbers dim when zero - "in"/"out" labels centered within their fixed-width slot so they no longer shift when counts change length or remote toggle changes totals Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --- .../dsl/jbang/core/commands/tui/CamelMonitor.java | 106 ++++++++++++++------- 1 file changed, 69 insertions(+), 37 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 2f3da9f81ac9..65ddec4afe86 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 @@ -48,6 +48,7 @@ import dev.tamboui.layout.Alignment; import dev.tamboui.layout.Constraint; import dev.tamboui.layout.Layout; import dev.tamboui.layout.Rect; +import dev.tamboui.style.AnsiColor; import dev.tamboui.style.Color; import dev.tamboui.style.Overflow; import dev.tamboui.style.Style; @@ -169,12 +170,18 @@ public class CamelMonitor extends CamelCommand { // Track last time a sparkline point was recorded private final Map<String, Long> previousExchangesTime = new ConcurrentHashMap<>(); - // Endpoint in/out sliding window history per PID (one point per second, 20 points) + // Endpoint in/out sliding window history per PID — all endpoints private final Map<String, LinkedList<Long>> endpointInHistory = new ConcurrentHashMap<>(); private final Map<String, LinkedList<Long>> endpointOutHistory = new ConcurrentHashMap<>(); private final Map<String, LinkedList<long[]>> endpointSamples = new ConcurrentHashMap<>(); private final Map<String, Long> previousEndpointTime = new ConcurrentHashMap<>(); + // Endpoint in/out sliding window history per PID — remote endpoints only + private final Map<String, LinkedList<Long>> endpointRemoteInHistory = new ConcurrentHashMap<>(); + private final Map<String, LinkedList<Long>> endpointRemoteOutHistory = new ConcurrentHashMap<>(); + private final Map<String, LinkedList<long[]>> endpointRemoteSamples = new ConcurrentHashMap<>(); + private final Map<String, Long> previousEndpointRemoteTime = new ConcurrentHashMap<>(); + // Overview sort state private String overviewSort = "name"; private int overviewSortIndex = 1; @@ -1290,26 +1297,26 @@ public class CamelMonitor extends CamelCommand { Span.raw(" ["), Span.styled(chartName, Style.EMPTY.fg(Color.YELLOW)), Span.raw(String.format("] Throughput: %d msg/s ", curTp)), - Span.styled("■", Style.EMPTY.fg(Color.GREEN)), + Span.styled("■", Style.EMPTY.fg(Color.ansi(AnsiColor.BRIGHT_GREEN))), Span.raw(String.format(" ok:%d ", curOk)), Span.styled("■", Style.EMPTY.fg(Color.RED)), Span.raw(String.format(" fail:%d ", curFailed))); } else { titleLine = Line.from( Span.raw(String.format(" [All] Throughput: %d msg/s ", curTp)), - Span.styled("■", Style.EMPTY.fg(Color.GREEN)), + Span.styled("■", Style.EMPTY.fg(Color.ansi(AnsiColor.BRIGHT_GREEN))), Span.raw(String.format(" ok:%d ", curOk)), Span.styled("■", Style.EMPTY.fg(Color.RED)), Span.raw(String.format(" fail:%d ", curFailed))); } - // Build bar groups (ok=green, failed=red), no bar value labels + // Build bar groups (ok=bright green, failed=red), no bar value labels List<BarGroup> groups = new ArrayList<>(); for (int i = 0; i < renderPoints; i++) { long failed = Math.min(mergedFailed[i], mergedTotal[i]); long ok = Math.max(0, mergedTotal[i] - failed); groups.add(BarGroup.of( - Bar.builder().value(ok).textValue("").style(Style.EMPTY.fg(Color.GREEN)).build(), + Bar.builder().value(ok).textValue("").style(Style.EMPTY.fg(Color.ansi(AnsiColor.BRIGHT_GREEN))).build(), Bar.builder().value(failed).textValue("").style(Style.EMPTY.fg(Color.RED)).build())); } @@ -2239,7 +2246,7 @@ public class CamelMonitor extends CamelCommand { } return switch (type) { case "from" -> Color.GREEN; - case "to", "toD", "wireTap", "enrich", "pollEnrich" -> Color.BLUE; + case "to", "toD", "wireTap", "enrich", "pollEnrich" -> Color.CYAN; case "choice", "when", "otherwise" -> Color.YELLOW; case "marshal", "unmarshal", "transform", "setBody", "setHeader", "setProperty", "convertBodyTo", "removeHeader", "removeHeaders", "removeProperty", "removeProperties" -> @@ -2918,8 +2925,8 @@ public class CamelMonitor extends CamelCommand { for (EndpointInfo ep : sortedEndpoints) { String dir = ep.direction != null ? ep.direction : ""; Style dirStyle = switch (dir) { - case "in" -> Style.EMPTY.fg(Color.GREEN); - case "out" -> Style.EMPTY.fg(Color.BLUE); + case "in" -> Style.EMPTY.fg(Color.ansi(AnsiColor.BRIGHT_GREEN)); + case "out" -> Style.EMPTY.fg(Color.CYAN); default -> Style.EMPTY.fg(Color.YELLOW); }; String arrow = switch (dir) { @@ -2980,17 +2987,18 @@ public class CamelMonitor extends CamelCommand { frame.renderStatefulWidget(table, chunks.get(0), endpointTableState); long inTotal = info.endpoints.stream() - .filter(ep -> "in".equals(ep.direction)) + .filter(ep -> "in".equals(ep.direction) && (!showOnlyRemote || ep.remote)) .mapToLong(ep -> ep.hits) .sum(); long outTotal = info.endpoints.stream() - .filter(ep -> "out".equals(ep.direction)) + .filter(ep -> "out".equals(ep.direction) && (!showOnlyRemote || ep.remote)) .mapToLong(ep -> ep.hits) .sum(); - renderEndpointFlow(frame, chunks.get(1), inTotal, outTotal, info.name, info.pid); + renderEndpointFlow(frame, chunks.get(1), inTotal, outTotal, info.name, info.pid, showOnlyRemote); } - private void renderEndpointFlow(Frame frame, Rect area, long inTotal, long outTotal, String name, String pid) { + private void renderEndpointFlow( + Frame frame, Rect area, long inTotal, long outTotal, String name, String pid, boolean remoteOnly) { List<Rect> hSplit = Layout.horizontal() .constraints(Constraint.length(38), Constraint.fill()) .split(area); @@ -3016,8 +3024,14 @@ public class CamelMonitor extends CamelCommand { int centerGap = boxLen + 2; int outPad = Math.max(0, sideLen - outStr.length()); - Style inStyle = Style.EMPTY.fg(Color.GREEN); - Style outStyle = Style.EMPTY.fg(Color.BLUE); + // Centre "in"/"out" labels within the fixed sideLen slot, independent of count width + int inLabelPad = (sideLen - 2) / 2; + int outLabelPad = (sideLen - 3) / 2; + String inLabelStr = " ".repeat(inLabelPad) + "in" + " ".repeat(sideLen - inLabelPad - 2); + String outLabelStr = " ".repeat(outLabelPad) + "out"; + + Style inStyle = Style.EMPTY.fg(Color.ansi(AnsiColor.BRIGHT_GREEN)); + Style outStyle = Style.EMPTY.fg(Color.CYAN); Style dimStyle = Style.EMPTY.dim(); List<Line> flowLines = new ArrayList<>(); @@ -3026,15 +3040,15 @@ public class CamelMonitor extends CamelCommand { Span.raw(" ".repeat(centerGap)), Span.styled(outStr + " ".repeat(outPad), outTotal > 0 ? outStyle : dimStyle))); flowLines.add(Line.from( - Span.styled(arrowStr, inTotal > 0 ? inStyle : dimStyle), + Span.styled(arrowStr, inStyle), Span.raw(" "), - Span.styled(box, Style.EMPTY.fg(Color.CYAN).bold()), + Span.styled(box, Style.EMPTY.fg(Color.YELLOW).bold()), Span.raw(" "), - Span.styled(arrowStr, outTotal > 0 ? outStyle : dimStyle))); + Span.styled(arrowStr, outStyle))); flowLines.add(Line.from( - Span.styled(" ".repeat(inPad) + "in", inTotal > 0 ? inStyle.dim() : dimStyle), + Span.styled(inLabelStr, inStyle.dim()), Span.raw(" ".repeat(centerGap)), - Span.styled("out" + " ".repeat(Math.max(0, outPad - 2)), outTotal > 0 ? outStyle.dim() : dimStyle))); + Span.styled(outLabelStr, outStyle.dim()))); frame.renderWidget(Paragraph.builder() .text(Text.from(flowLines)) @@ -3042,8 +3056,10 @@ public class CamelMonitor extends CamelCommand { .build(), hSplit.get(0)); // --- Right: 60-second sliding window chart (in=green up, out=blue down) --- - LinkedList<Long> inHist = endpointInHistory.getOrDefault(pid, new LinkedList<>()); - LinkedList<Long> outHist = endpointOutHistory.getOrDefault(pid, new LinkedList<>()); + LinkedList<Long> inHist = (remoteOnly ? endpointRemoteInHistory : endpointInHistory) + .getOrDefault(pid, new LinkedList<>()); + LinkedList<Long> outHist = (remoteOnly ? endpointRemoteOutHistory : endpointOutHistory) + .getOrDefault(pid, new LinkedList<>()); int renderPoints = MAX_ENDPOINT_CHART_POINTS; long[] inArr = new long[renderPoints]; @@ -3062,17 +3078,17 @@ public class CamelMonitor extends CamelCommand { long curOut = outArr[renderPoints - 1]; Line chartTitle = Line.from( - Span.styled("▬", Style.EMPTY.fg(Color.GREEN)), + Span.styled("▬", Style.EMPTY.fg(Color.ansi(AnsiColor.BRIGHT_GREEN))), Span.raw(String.format(" in:%-4d ", curIn)), - Span.styled("▬", Style.EMPTY.fg(Color.BLUE)), + Span.styled("▬", Style.EMPTY.fg(Color.CYAN)), Span.raw(String.format(" out:%-4d msg/s", curOut))); Rect rightArea = hSplit.get(1); frame.renderWidget(MirroredSparkline.builder() .topData(inArr) .bottomData(outArr) - .topStyle(Style.EMPTY.fg(Color.GREEN)) - .bottomStyle(Style.EMPTY.fg(Color.BLUE)) + .topStyle(Style.EMPTY.fg(Color.ansi(AnsiColor.BRIGHT_GREEN))) + .bottomStyle(Style.EMPTY.fg(Color.CYAN)) .xLabels("-" + renderPoints + "s", "-" + (renderPoints * 3 / 4) + "s", "-" + (renderPoints / 2) + "s", "-" + (renderPoints / 4) + "s", "now") .block(Block.builder().borderType(BorderType.ROUNDED) @@ -4123,6 +4139,10 @@ public class CamelMonitor extends CamelCommand { endpointOutHistory.remove(entry.getKey()); endpointSamples.remove(entry.getKey()); previousEndpointTime.remove(entry.getKey()); + endpointRemoteInHistory.remove(entry.getKey()); + endpointRemoteOutHistory.remove(entry.getKey()); + endpointRemoteSamples.remove(entry.getKey()); + previousEndpointRemoteTime.remove(entry.getKey()); } else if (!livePids.contains(entry.getKey())) { IntegrationInfo ghost = entry.getValue().info; ghost.vanishing = true; @@ -4213,38 +4233,50 @@ public class CamelMonitor extends CamelCommand { private void updateEndpointHistory(IntegrationInfo info) { long inTotal = info.endpoints.stream() .filter(ep -> "in".equals(ep.direction)) - .mapToLong(ep -> ep.hits) - .sum(); + .mapToLong(ep -> ep.hits).sum(); long outTotal = info.endpoints.stream() .filter(ep -> "out".equals(ep.direction)) - .mapToLong(ep -> ep.hits) - .sum(); + .mapToLong(ep -> ep.hits).sum(); + long inRemote = info.endpoints.stream() + .filter(ep -> "in".equals(ep.direction) && ep.remote) + .mapToLong(ep -> ep.hits).sum(); + long outRemote = info.endpoints.stream() + .filter(ep -> "out".equals(ep.direction) && ep.remote) + .mapToLong(ep -> ep.hits).sum(); long now = System.currentTimeMillis(); String pid = info.pid; - LinkedList<long[]> samples = endpointSamples.computeIfAbsent(pid, k -> new LinkedList<>()); - samples.add(new long[] { now, inTotal, outTotal }); + recordEndpointSample(pid, now, inTotal, outTotal, + endpointSamples, previousEndpointTime, endpointInHistory, endpointOutHistory); + recordEndpointSample(pid, now, inRemote, outRemote, + endpointRemoteSamples, previousEndpointRemoteTime, endpointRemoteInHistory, endpointRemoteOutHistory); + } + + private void recordEndpointSample( + String pid, long now, long inTotal, long outTotal, + Map<String, LinkedList<long[]>> samplesMap, Map<String, Long> prevTimeMap, + Map<String, LinkedList<Long>> inHistMap, Map<String, LinkedList<Long>> outHistMap) { + LinkedList<long[]> samples = samplesMap.computeIfAbsent(pid, k -> new LinkedList<>()); + samples.add(new long[] { now, inTotal, outTotal }); while (!samples.isEmpty() && now - samples.get(0)[0] > 1000) { samples.remove(0); } - if (samples.size() >= 2) { long[] oldest = samples.get(0); long[] newest = samples.get(samples.size() - 1); long deltaMs = newest[0] - oldest[0]; long inRate = deltaMs > 0 ? (newest[1] - oldest[1]) * 1000 / deltaMs : 0; long outRate = deltaMs > 0 ? (newest[2] - oldest[2]) * 1000 / deltaMs : 0; - - Long lastTime = previousEndpointTime.get(pid); + Long lastTime = prevTimeMap.get(pid); if (lastTime == null || now - lastTime >= 1000) { - previousEndpointTime.put(pid, now); - LinkedList<Long> inHist = endpointInHistory.computeIfAbsent(pid, k -> new LinkedList<>()); + prevTimeMap.put(pid, now); + LinkedList<Long> inHist = inHistMap.computeIfAbsent(pid, k -> new LinkedList<>()); inHist.add(Math.max(0, inRate)); while (inHist.size() > MAX_ENDPOINT_CHART_POINTS) { inHist.remove(0); } - LinkedList<Long> outHist = endpointOutHistory.computeIfAbsent(pid, k -> new LinkedList<>()); + LinkedList<Long> outHist = outHistMap.computeIfAbsent(pid, k -> new LinkedList<>()); outHist.add(Math.max(0, outRate)); while (outHist.size() > MAX_ENDPOINT_CHART_POINTS) { outHist.remove(0);
