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 e0ecb1cfe2a2 CAMEL-25426: camel-jbang - the monitor's tables keep
names readable and cut text with an ellipsis (#27519)
e0ecb1cfe2a2 is described below
commit e0ecb1cfe2a254e7c505ac4ae3400338f91e33e3
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 8 01:03:42 2026 +0200
CAMEL-25426: camel-jbang - the monitor's tables keep names readable and cut
text with an ellipsis (#27519)
* CAMEL-25426: camel-jbang - the monitor's Overview keeps the NAME column
on a narrow screen
At 100 columns the Overview dropped the NAME column, so the apps showed by
pid only. NAME now keeps room for a name: on a narrow screen the timings,
the rate and the version give way first. A name that does not fit ends with
an ellipsis (an opened project's "(project)" suffix was cut silently).
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
* CAMEL-25426: camel-jbang - the monitor's Routes tables cut long text with
an ellipsis
The Routes table cut its TOTAL and FAIL columns before the closing
parenthesis ("3 (18m30s"), and long route ids and URIs without an ellipsis,
so a cut URI read as the whole URI. TOTAL and FAIL are now as wide as their
longest value, the timing columns are fixed so the URI column gets the rest
of the width, and route ids, URIs and processor ids end with an ellipsis
where they are cut.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
* CAMEL-25426: camel-jbang - the monitor's Errors table has room for the
message, and empty tables say so in full
The Errors table gave the full exchange id 38 columns, which left the
message 8 ("card dec" for "card declined"), and COUNT was blank for an
error seen once. The id is now short, as on the Activity tab (the detail
shows the full id), and COUNT shows 1.
A table that is empty said so in its first column, which cut the message
where that column is narrow ("No infligh"). These tables now say it in
their widest column.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
* CAMEL-25426: camel-jbang - the monitor's Consumers tab shows the consumer
type again
The TYPE column was empty on every row: the consumers status names the
class clazz, which the monitor did not read (producers too). REMOTE now uses
the check mark the Endpoints tab uses for the same flag, and long route ids
and URIs end with an ellipsis where they are cut.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
* CAMEL-25426: camel-jbang - the monitor's Open Project dialog is wide
enough for a folder path
The dialog was 70 columns wide, so a remembered folder showed cut at its
start (s/davsclaus/workspace/...). It is now up to 100 columns wide.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
* CAMEL-25426: camel-jbang - the Errors tab tests expect the short exchange
id
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
---------
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
.../dsl/jbang/core/commands/tui/AbstractTab.java | 13 +++--
.../dsl/jbang/core/commands/tui/ActivityTab.java | 2 +-
.../dsl/jbang/core/commands/tui/ConsumersTab.java | 9 ++--
.../dsl/jbang/core/commands/tui/CveAuditTab.java | 2 +-
.../dsl/jbang/core/commands/tui/ErrorsTab.java | 10 ++--
.../jbang/core/commands/tui/FolderInputPopup.java | 3 +-
.../jbang/core/commands/tui/HeapHistogramTab.java | 2 +-
.../dsl/jbang/core/commands/tui/InflightTab.java | 2 +-
.../dsl/jbang/core/commands/tui/MetricsTab.java | 2 +-
.../dsl/jbang/core/commands/tui/OverviewTab.java | 61 ++++++++++++++++------
.../dsl/jbang/core/commands/tui/RoutesTab.java | 47 ++++++++++++-----
.../dsl/jbang/core/commands/tui/StatusParser.java | 5 +-
.../dsl/jbang/core/commands/tui/ThreadsTab.java | 2 +-
.../dsl/jbang/core/commands/tui/TuiHelper.java | 25 +++++++++
.../core/commands/tui/ErrorsTabRenderTest.java | 14 ++---
15 files changed, 144 insertions(+), 55 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTab.java
index 8b18bf09c37e..9f8c5b126a99 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTab.java
@@ -110,10 +110,17 @@ abstract class AbstractTab implements MonitorTab {
}
protected static Row emptyRow(String message, int columnCount) {
+ return emptyRow(message, columnCount, 0);
+ }
+
+ /**
+ * A row that says the table is empty, with the message in the given
column: a table whose first column is narrow
+ * puts it in its widest column, so it is not cut ("No infligh").
+ */
+ protected static Row emptyRow(String message, int columnCount, int column)
{
Cell[] cells = new Cell[columnCount];
- cells[0] = Cell.from(Span.styled(message, Style.EMPTY.dim()));
- for (int i = 1; i < columnCount; i++) {
- cells[i] = Cell.from("");
+ for (int i = 0; i < columnCount; i++) {
+ cells[i] = i == column ? Cell.from(Span.styled(message,
Style.EMPTY.dim())) : Cell.from("");
}
return Row.from(cells);
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActivityTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActivityTab.java
index 59b5bbcccb9c..da3aa7c5b614 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActivityTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActivityTab.java
@@ -155,7 +155,7 @@ class ActivityTab extends AbstractTableTab {
}
if (rows.isEmpty()) {
- rows.add(emptyRow("No activity captured", 7));
+ rows.add(emptyRow("No activity captured", 7, 6));
}
ActivityEntry selectedEntry = null;
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
index d3af7c3d1dc8..edcdb8744e22 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
@@ -78,16 +78,19 @@ class ConsumersTab extends AbstractTableTab {
? hc.message
: (ci.uri != null ? ci.uri : "");
+ // the URI gets the rest of the width: the borders, ">> ", 8 gaps
and the other columns; a long id or URI
+ // ends with an ellipsis where it is cut
+ int uriWidth = Math.max(0, area.width() - 2 - 3 - 8 - (20 + 10 +
16 + 8 + 8 + 22 + 8 + 22));
rows.add(Row.from(
- Cell.from(Span.styled(" " + (ci.id != null ? ci.id : ""),
Style.EMPTY.fg(Theme.accent()))),
+ Cell.from(Span.styled(" " + TuiHelper.truncate(ci.id, 19),
Style.EMPTY.fg(Theme.accent()))),
Cell.from(Span.styled(statusText, statusStyle)),
Cell.from(type),
- Cell.from(ci.remote ? "x" : ""),
+ Cell.from(ci.remote ? TuiIcons.CHECK : ""),
rightCell(String.valueOf(ci.inflight), 8),
Cell.from(schedule),
rightCell(ci.totalCounter != null ?
String.valueOf(ci.totalCounter) : "", 8),
Cell.from(sinceLast),
- Cell.from(Span.styled(uri, healthDown ? Theme.error() :
Style.EMPTY))));
+ Cell.from(Span.styled(TuiHelper.truncate(uri, uriWidth),
healthDown ? Theme.error() : Style.EMPTY))));
}
if (rows.isEmpty()) {
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
index b161da30df73..3186fad5b0d3 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
@@ -251,7 +251,7 @@ class CveAuditTab extends AbstractTableTab {
}
if (rows.isEmpty() && dataLoaded) {
- rows.add(emptyRow("No matching vulnerabilities", 5));
+ rows.add(emptyRow("No matching vulnerabilities", 5, 4));
}
String title = buildTitle();
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
index 0a4d1d7a874a..ad9478816b41 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
@@ -351,19 +351,21 @@ class ErrorsTab extends AbstractTableTab {
String shortException = shortExceptionType(ei.exceptionType);
rows.add(Row.from(
- Cell.from(ei.exchangeId != null ? ei.exchangeId : ""),
+ // short, as on the Activity tab, so MESSAGE has room; the
detail below has the full id
+ Cell.from(ActivityTab.shortExchangeId(ei.exchangeId !=
null ? ei.exchangeId : "")),
Cell.from(ago),
Cell.from(Span.styled(ei.routeId != null ? ei.routeId :
"", Style.EMPTY.fg(Theme.accent()))),
Cell.from(ei.nodeId != null ? ei.nodeId : ""),
Cell.from(Span.styled(handledStr, handledStyle)),
Cell.from(ei.repeatCount > 1
- ? Span.styled(Long.toString(ei.repeatCount),
Theme.error().bold()) : Span.raw("")),
+ ? Span.styled(Long.toString(ei.repeatCount),
Theme.error().bold())
+ : Span.raw(Long.toString(Math.max(1,
ei.repeatCount)))),
Cell.from(shortException),
Cell.from(ei.exceptionMessage != null ?
ei.exceptionMessage : "")));
}
if (rows.isEmpty()) {
- rows.add(emptyRow("No errors captured", 8));
+ rows.add(emptyRow("No errors captured", 8, 7));
}
ErrorInfo selectedError = null;
@@ -390,7 +392,7 @@ class ErrorsTab extends AbstractTableTab {
Cell.from(Span.styled(sortLabel("EXCEPTION",
"exception"), sortStyle("exception"))),
Cell.from(Span.styled("MESSAGE", Style.EMPTY.bold()))))
.widths(
- Constraint.length(38),
+ Constraint.length(14),
Constraint.length(8),
Constraint.length(20),
Constraint.length(20),
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderInputPopup.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderInputPopup.java
index 58fad1cc8979..626386949b06 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderInputPopup.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderInputPopup.java
@@ -327,7 +327,8 @@ class FolderInputPopup {
}
private void renderInput(Frame frame, Rect area) {
- int popupW = Math.min(70, area.width() - 4);
+ // wide enough for a folder path, so a remembered folder is not cut at
its start
+ int popupW = Math.min(100, area.width() - 4);
Rect popup = DialogHelper.centered(area, popupW,
DialogHelper.INPUT_HEIGHT);
frame.renderWidget(Clear.INSTANCE, popup);
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HeapHistogramTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HeapHistogramTab.java
index 52927e66acb9..eb066ebab35c 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HeapHistogramTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HeapHistogramTab.java
@@ -135,7 +135,7 @@ class HeapHistogramTab extends AbstractTableTab {
}
if (rows.isEmpty()) {
- rows.add(emptyRow("No data", 4));
+ rows.add(emptyRow("No data", 4, 1));
}
long visibleInstances = 0;
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java
index 323bc95e0029..9198259a6ace 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java
@@ -103,7 +103,7 @@ class InflightTab extends AbstractTableTab {
}
if (rows.isEmpty()) {
- rows.add(emptyRow("No inflight or blocked exchanges", 5));
+ rows.add(emptyRow("No inflight or blocked exchanges", 5, 2));
}
String title = " Inflight (" + sorted.size() + ") ";
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
index 1733228dbd60..f589615086ba 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
@@ -489,7 +489,7 @@ class MetricsTab extends AbstractTableTab {
}
if (rows.isEmpty()) {
- rows.add(emptyRow("No " + filterType + " metrics", 4));
+ rows.add(emptyRow("No " + filterType + " metrics", 4, 3));
}
String title = " Metrics";
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
index abd201df7753..82cfb767fafa 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
@@ -31,6 +31,7 @@ import dev.tamboui.layout.Rect;
import dev.tamboui.style.Color;
import dev.tamboui.style.Style;
import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.CharWidth;
import dev.tamboui.text.Line;
import dev.tamboui.text.Span;
import dev.tamboui.text.Text;
@@ -326,7 +327,7 @@ class OverviewTab extends AbstractTab {
vSplit.clearBorderPos();
}
- TableSpec spec = topMode ? buildTopModeTable(infos) :
buildOverviewTable(infos);
+ TableSpec spec = topMode ? buildTopModeTable(infos) :
buildOverviewTable(infos, area.width());
String integrationTitle = infraCount > 0 ? " Integrations " : "
Overview ";
Style intBorderStyle = ctx.paneBorder(!infraFocused);
@@ -512,7 +513,7 @@ class OverviewTab extends AbstractTab {
/**
* Rows, header and column widths for the integrations table in the
default overview mode.
*/
- private TableSpec buildOverviewTable(List<IntegrationInfo> infos) {
+ private TableSpec buildOverviewTable(List<IntegrationInfo> infos, int
areaWidth) {
List<Row> rows = new ArrayList<>();
int rowIndex = 0;
boolean hasPercentiles = infos.stream().anyMatch(i -> i.p50Time >= 0);
@@ -523,6 +524,25 @@ class OverviewTab extends AbstractTab {
// TOTAL and FAIL are as wide as the longest "count (since last)", so
the since part is not cut
int totalColWidth = 14;
int failColWidth = 14;
+ for (IntegrationInfo info : infos) {
+ if (!info.phantom && !info.vanishing) {
+ int since = info.sinceLastCompleted != null ?
CharWidth.of(info.sinceLastCompleted) + 3 : 0;
+ int failSince = info.sinceLastFailed != null ?
CharWidth.of(info.sinceLastFailed) + 3 : 0;
+ totalColWidth = Math.max(totalColWidth, tw + since + 1);
+ failColWidth = Math.max(failColWidth, fw + failSince + 1);
+ }
+ }
+ // NAME tells the apps apart, so it is the column that stays: on a
narrow screen the timings go first, then
+ // the rate and the version, so NAME keeps room for a name
+ int[] fixed = { 8, 16, 7, 10, 7, 8, totalColWidth, failColWidth, 20 };
+ int nameWidth = overviewNameWidth(areaWidth, fixed);
+ for (int drop : new int[] { 8, 5, 1 }) {
+ if (nameWidth >= MIN_NAME_WIDTH) {
+ break;
+ }
+ fixed[drop] = 0;
+ nameWidth = overviewNameWidth(areaWidth, fixed);
+ }
for (IntegrationInfo info : infos) {
boolean isEven = (rowIndex++ % 2 == 0);
Style rowBg = isEven ? Style.EMPTY.bg(Theme.zebra()) : Style.EMPTY;
@@ -532,7 +552,7 @@ class OverviewTab extends AbstractTab {
String nameText = platformIcon + " " + (info.name != null ?
info.name : "");
rows.add(Row.from(
Cell.from(Span.styled("-", Theme.muted())),
- Cell.from(Span.styled(nameText, Theme.info())),
+ Cell.from(Span.styled(TuiHelper.truncate(nameText,
nameWidth), Theme.info())),
Cell.from(Span.styled("", Theme.muted())),
Cell.from(Span.styled("", Theme.muted())),
Cell.from(projectStatus(info,
System.currentTimeMillis())),
@@ -600,7 +620,7 @@ class OverviewTab extends AbstractTab {
if (hasDoc) {
nameSpans.add(Span.styled(" " + TuiIcons.README,
Style.EMPTY));
}
- Line nameLine = Line.from(nameSpans);
+ Line nameLine = TuiHelper.fitLine(nameSpans, nameWidth);
String throughputDisplay = info.throughput;
if (throughputDisplay == null ||
"0.00".equals(throughputDisplay)) {
LinkedList<Long> tpHist = throughputHistory.get(info.pid);
@@ -632,8 +652,6 @@ class OverviewTab extends AbstractTab {
? Line.from(Span.styled(String.format("%" + fw + "d",
info.failed), failStyle),
Span.styled(" (" + info.sinceLastFailed + ")",
Theme.muted()))
: Line.from(Span.styled(String.format("%" + fw + "d",
info.failed), failStyle));
- totalColWidth = Math.max(totalColWidth, totalCell.width() + 1);
- failColWidth = Math.max(failColWidth, failCell.width() + 1);
rows.add(Row.from(
Cell.from(info.pid),
@@ -665,21 +683,34 @@ class OverviewTab extends AbstractTab {
Cell.from(""));
Constraint[] widths = new Constraint[] {
- Constraint.length(8),
+ Constraint.length(fixed[0]),
Constraint.fill(),
- Constraint.length(16),
- Constraint.length(7),
- Constraint.length(10),
- Constraint.length(7),
- Constraint.length(8),
- Constraint.length(totalColWidth),
- Constraint.length(failColWidth),
- Constraint.min(20),
+ Constraint.length(fixed[1]),
+ Constraint.length(fixed[2]),
+ Constraint.length(fixed[3]),
+ Constraint.length(fixed[4]),
+ Constraint.length(fixed[5]),
+ Constraint.length(fixed[6]),
+ Constraint.length(fixed[7]),
+ // fixed, so NAME gets the rest of the width, as
overviewNameWidth counts it
+ Constraint.length(fixed[8]),
Constraint.length(0)
};
return new TableSpec(rows, header, widths);
}
+ // the narrowest NAME before other columns give way: room for an icon and
a name
+ static final int MIN_NAME_WIDTH = 20;
+
+ /** The width the NAME column gets beside the other columns: the table's
borders, ">> " and 1 between columns. */
+ static int overviewNameWidth(int areaWidth, int[] fixed) {
+ int used = 2 + 3 + 10;
+ for (int w : fixed) {
+ used += w;
+ }
+ return Math.max(0, areaWidth - used);
+ }
+
/**
* Renders the throughput bar chart (ok/failed per second over time) with
its y axis, time markers and the info
* panel to its right.
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
index e0f121cdc16d..1187897c2cb0 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
@@ -27,6 +27,7 @@ import dev.tamboui.layout.Layout;
import dev.tamboui.layout.Rect;
import dev.tamboui.style.Style;
import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.CharWidth;
import dev.tamboui.text.Line;
import dev.tamboui.text.Span;
import dev.tamboui.text.Text;
@@ -564,7 +565,7 @@ class RoutesTab extends AbstractTab {
.split(area);
// Routes table
- Table routeTable = routeTopMode ? buildRouteTopTable(sortedRoutes) :
buildRouteTable(sortedRoutes);
+ Table routeTable = routeTopMode ? buildRouteTopTable(sortedRoutes) :
buildRouteTable(sortedRoutes, area.width());
lastRouteTableArea = chunks.get(0);
vSplit.setBorderPos(chunks.get(1).y());
@@ -681,7 +682,7 @@ class RoutesTab extends AbstractTab {
routeRows.add(Row.from(
Cell.from(Span.styled(route.routeId != null ?
route.routeId : "", Style.EMPTY.fg(Theme.accent()))),
- routeFromCell(route, aiDescriptions),
+ routeFromCell(route, aiDescriptions, Integer.MAX_VALUE),
rightCell(route.total > 0 ?
formatDurationMs(route.meanTime) : "", 8,
topTimeStyle(route.meanTime)),
rightCell(route.total > 0 ?
formatDurationMs(route.maxTime) : "", 8,
@@ -767,13 +768,24 @@ class RoutesTab extends AbstractTab {
/**
* The routes table in the default mode (status, counters, timing).
*/
- private Table buildRouteTable(List<RouteInfo> sortedRoutes) {
+ private Table buildRouteTable(List<RouteInfo> sortedRoutes, int areaWidth)
{
boolean hasPercentiles = sortedRoutes.stream().anyMatch(r -> r.p50Time
>= 0);
long maxTotal = sortedRoutes.stream().mapToLong(r ->
r.total).max().orElse(0);
long maxFailed = sortedRoutes.stream().mapToLong(r ->
r.failed).max().orElse(0);
int tw = Math.max(numWidth(maxTotal), 6);
int fw = Math.max(numWidth(maxFailed), 6);
+ // TOTAL and FAIL are as wide as the longest "count (since last)", so
the closing parenthesis is not cut
+ int totalColWidth = 14;
+ int failColWidth = 14;
+ for (RouteInfo route : sortedRoutes) {
+ int since = route.sinceLastCompleted != null ?
CharWidth.of(route.sinceLastCompleted) + 3 : 0;
+ int failSince = route.sinceLastFailed != null ?
CharWidth.of(route.sinceLastFailed) + 3 : 0;
+ totalColWidth = Math.max(totalColWidth, tw + since + 1);
+ failColWidth = Math.max(failColWidth, fw + failSince + 1);
+ }
+ // FROM gets the rest of the width: the borders, ">> ", 7 gaps and the
other columns
+ int fromWidth = Math.max(0, areaWidth - 2 - 3 - 7 - (24 + 10 + 10 +
totalColWidth + failColWidth + 20 + 12));
List<Row> routeRows = new ArrayList<>();
Map<String, String> aiDescriptions = aiDescriptions();
@@ -807,8 +819,8 @@ class RoutesTab extends AbstractTab {
: Line.from(Span.styled(String.format("%" + fw + "d",
route.failed), failStyle));
routeRows.add(Row.from(
- Cell.from(Span.styled(route.routeId != null ?
route.routeId : "", Style.EMPTY.fg(Theme.accent()))),
- routeFromCell(route, aiDescriptions),
+ Cell.from(Span.styled(TuiHelper.truncate(route.routeId,
24), Style.EMPTY.fg(Theme.accent()))),
+ routeFromCell(route, aiDescriptions, fromWidth),
Cell.from(Span.styled(route.state != null ? route.state :
"", stateStyle)),
rightCell(formatThroughput(route.throughput), 8),
Cell.from(totalCell),
@@ -858,9 +870,9 @@ class RoutesTab extends AbstractTab {
Constraint.fill(),
Constraint.length(10),
Constraint.length(10),
- Constraint.length(14),
- Constraint.length(14),
- Constraint.min(20),
+ Constraint.length(totalColWidth),
+ Constraint.length(failColWidth),
+ Constraint.length(20),
Constraint.length(12))
.highlightStyle(Theme.selectionBg())
.highlightSpacing(Table.HighlightSpacing.ALWAYS)
@@ -969,14 +981,16 @@ class RoutesTab extends AbstractTab {
* The from or description cell. A route without a description of its own
shows the one the AI project overview
* suggested, marked and styled as AI-assisted so it is not taken for what
the route says (CAMEL-25143).
*/
- private Cell routeFromCell(RouteInfo route, Map<String, String>
aiDescriptions) {
+ private Cell routeFromCell(RouteInfo route, Map<String, String>
aiDescriptions, int width) {
if (showDescription && (route.description == null ||
route.description.isBlank())) {
String ai = route.routeId != null ?
aiDescriptions.get(route.routeId) : null;
if (ai != null) {
- return Cell.from(Span.styled(IntegrationSummaryHints.MARK +
ai, Theme.aiAssisted()));
+ return
Cell.from(Span.styled(TuiHelper.truncate(IntegrationSummaryHints.MARK + ai,
width),
+ Theme.aiAssisted()));
}
}
- return Cell.from(routeFromLabel(route));
+ // a long URI ends with an ellipsis where it is cut, so it does not
read as the whole URI
+ return Cell.from(TuiHelper.truncate(routeFromLabel(route), width));
}
/** The AI-assisted descriptions of the selected integration's project,
read once per table. */
@@ -1138,9 +1152,13 @@ class RoutesTab extends AbstractTab {
} else {
routeTimingCol = "";
}
+ // PROCESSOR gets the rest of the width: the borders, ">> ", 7
gaps and the other columns; a long URI
+ // ends with an ellipsis where it is cut
+ int procWidth = Math.max(0, area.width() - 2 - 3 - 7 - (20 + 10 +
8 + 6 + 8 + 20 + 12));
rows.add(Row.from(
Cell.from(" route"),
- Cell.from(Span.styled(route.from != null ?
TuiHelper.displayUri(route.from) : route.routeId,
+ Cell.from(Span.styled(TuiHelper.truncate(
+ route.from != null ?
TuiHelper.displayUri(route.from) : route.routeId, procWidth),
routeStyle)),
rightCell(formatThroughput(route.throughput), 8),
rightCell(String.valueOf(route.total), 8),
@@ -1167,7 +1185,8 @@ class RoutesTab extends AbstractTab {
rows.add(Row.from(
Cell.from(" " + (proc.processor != null ?
proc.processor : "")),
- Cell.from(Span.styled(indent + (proc.id != null ?
proc.id : ""), nameStyle)),
+ Cell.from(Span.styled(TuiHelper.truncate(indent +
(proc.id != null ? proc.id : ""), procWidth),
+ nameStyle)),
rightCell(formatThroughput(proc.throughput), 8),
rightCell(String.valueOf(proc.total), 8),
rightCell(String.valueOf(proc.failed), 6,
@@ -1196,7 +1215,7 @@ class RoutesTab extends AbstractTab {
Constraint.length(8),
Constraint.length(6),
Constraint.length(8),
- Constraint.min(20),
+ Constraint.length(20),
Constraint.length(12))
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
.title(" Processors [" + route.routeId + "] ")
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
index d9db4a4cc70e..4032f0b3a70e 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
@@ -404,7 +404,8 @@ final class StatusParser {
ci.id = cj.getString("id");
ci.uri = cj.getString("uri");
ci.state = cj.getString("state");
- ci.className = cj.getString("class");
+ // the consumers console says clazz (class in older
versions)
+ ci.className = cj.getStringOrDefault("clazz",
cj.getString("class"));
ci.scheduled = Boolean.TRUE.equals(cj.get("scheduled"));
ci.remote = Boolean.TRUE.equals(cj.get("remote"));
ci.inflight = cj.getIntegerOrDefault("inflight", 0);
@@ -478,7 +479,7 @@ final class StatusParser {
ProducerInfo pi = new ProducerInfo();
pi.uri = pj.getString("uri");
pi.state = pj.getString("state");
- pi.className = pj.getString("class");
+ pi.className = pj.getStringOrDefault("clazz",
pj.getString("class"));
pi.routeId = pj.getString("routeId");
pi.stepId = pj.getString("stepId");
pi.remote = Boolean.TRUE.equals(pj.get("remote"));
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
index b404fb19bf1a..15b93e9c7e1a 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
@@ -194,7 +194,7 @@ class ThreadsTab extends AbstractTableTab {
}
if (rows.isEmpty()) {
- rows.add(emptyRow("No threads", 5));
+ rows.add(emptyRow("No threads", 5, 1));
}
String title = String.format(" Threads [%d/%d] peak:%d filter:%s ",
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
index 1a244a3ac33f..5b3940a11447 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
@@ -163,6 +163,31 @@ final class TuiHelper {
}
}
+ /**
+ * The spans cut to the given display width, ending with an ellipsis where
text is cut, so a cell that is too narrow
+ * says so instead of losing its end silently.
+ */
+ static Line fitLine(List<Span> spans, int max) {
+ List<Span> answer = new ArrayList<>();
+ int used = 0;
+ for (Span span : spans) {
+ int w = CharWidth.of(span.content());
+ if (used + w <= max) {
+ answer.add(span);
+ used += w;
+ } else {
+ int room = max - used;
+ if (room > 0) {
+ answer.add(Span.styled(
+ CharWidth.truncateWithEllipsis(span.content(),
room, CharWidth.TruncatePosition.END),
+ span.style()));
+ }
+ break;
+ }
+ }
+ return Line.from(answer);
+ }
+
static String truncate(String s, int max) {
if (s == null) {
return "";
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTabRenderTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTabRenderTest.java
index 3c42792afda3..6be4707ced8d 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTabRenderTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTabRenderTest.java
@@ -146,7 +146,7 @@ class ErrorsTabRenderTest {
ErrorsTab tab = new ErrorsTab(ctx);
String rendered = renderToString(tab, 160, 30);
assertEquals(0, tab.tableState.selected());
- assertTrue(rendered.contains("ID-001"), rendered);
+ assertTrue(rendered.contains(ActivityTab.shortExchangeId("ID-001")),
rendered);
}
@Test
@@ -157,8 +157,8 @@ class ErrorsTabRenderTest {
ErrorsTab tab = new ErrorsTab(ctx);
String rendered = renderToString(tab, 160, 30);
- assertTrue(rendered.contains("ID-AAA"), "Should render first error");
- assertTrue(rendered.contains("ID-BBB"), "Should render second error");
+ assertTrue(rendered.contains(ActivityTab.shortExchangeId("ID-AAA")),
"Should render first error");
+ assertTrue(rendered.contains(ActivityTab.shortExchangeId("ID-BBB")),
"Should render second error");
assertTrue(rendered.contains("IOException"), "Should render first
exception type");
assertTrue(rendered.contains("NullPointerException"), "Should render
second exception type");
}
@@ -240,14 +240,14 @@ class ErrorsTabRenderTest {
// Default shows all
String all = renderToString(tab, 160, 30);
- assertTrue(all.contains("ID-001"), "All filter should show unhandled
errors");
- assertTrue(all.contains("ID-002"), "All filter should show handled
errors");
+ assertTrue(all.contains(ActivityTab.shortExchangeId("ID-001")), "All
filter should show unhandled errors");
+ assertTrue(all.contains(ActivityTab.shortExchangeId("ID-002")), "All
filter should show handled errors");
// Press 'f' to filter to handled=true
tab.handleKeyEvent(KeyEvent.ofChar('f', KeyModifiers.NONE));
String handledOnly = renderToString(tab, 160, 30);
- assertTrue(handledOnly.contains("ID-002"), "handled filter should show
handled error");
- assertFalse(handledOnly.contains("ID-001"), "handled filter should
hide unhandled error");
+
assertTrue(handledOnly.contains(ActivityTab.shortExchangeId("ID-002")),
"handled filter should show handled error");
+
assertFalse(handledOnly.contains(ActivityTab.shortExchangeId("ID-001")),
"handled filter should hide unhandled error");
}
@Test