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 2c53ecd29bd3 CAMEL-23512: Fix TamboUI API misuse in
camel-jbang-plugin-tui (#23200)
2c53ecd29bd3 is described below
commit 2c53ecd29bd3787885d4b0287e8e1de6012d3a3f
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu May 14 08:26:57 2026 +0200
CAMEL-23512: Fix TamboUI API misuse in camel-jbang-plugin-tui (#23200)
* CAMEL-23512: Fix TamboUI API misuse in camel-jbang-plugin-tui
- Replace deprecated KeyEvent.character() with ke.string() for proper
Unicode support
- Use CharWidth for display width calculations instead of String.length()
- Fix TuiHelper.truncate() to use CharWidth.truncateWithEllipsis()
- Fix CamelCatalogTui.wrapText() and flowFields() to use CharWidth
- Fix CamelMonitor diagram width calculation to use CharWidth
- Enable mouse capture in CamelMonitor for diagram scrolling
- Move TableState for processors and route header to class fields
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* CAMEL-23512: Use TamboUI semantic key bindings instead of raw KeyCode
checks
Replace raw KeyCode checks with semantic binding methods so vim/emacs
key bindings are respected automatically via TamboUI's binding system.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* CAMEL-23512: Replace vim g/G keys with Home/End in Log tab
Use standard Home/End keys for jump-to-top and jump-to-end in the
log viewer instead of vim-style g/G for consistency with normal users.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* CAMEL-23512: Use Block with Borders.TOP_ONLY for catalog separator
Replace manual Unicode box-drawing character repetition with TamboUI's
Block widget using Borders.TOP_ONLY for idiomatic separator rendering.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* CAMEL-23512: Use Style.EMPTY constant instead of Style.create()
Avoid unnecessary method call overhead by using the Style.EMPTY
constant directly instead of the Style.create() factory method
which just returns EMPTY.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* CAMEL-23512: Use Paragraph scroll and word wrap for catalog description
Replace manual line slicing and custom wrapText() method with
Paragraph's built-in scroll() and Overflow.WRAP_WORD support.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* CAMEL-23512: Deduplicate footer rendering with hint helper methods
Extract hint(), hintLast(), and hintRefresh() helpers to eliminate
repeated Span.styled/Span.raw patterns across all six tab footers.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* CAMEL-23512: Move blocking I/O off the render thread
Move refreshData() and loadDiagramForSelectedRoute() to background
threads using TuiRunner.scheduler() to prevent UI freezes.
- refreshData() now runs on the scheduler thread with AtomicBoolean
guard against overlapping refreshes; results swapped to render thread
- loadDiagramForSelectedRoute() shows loading state immediately, then
runs file I/O, polling, and diagram rendering on the scheduler thread;
diagram state applied atomically via runOnRenderThread()
- Convert logLines/filteredLogEntries to swap-on-write pattern for
thread-safe reads from the render thread
- pollJsonResponse() Thread.sleep loop no longer blocks the render thread
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* CAMEL-23512: Use default scrollbar style for diagram scrollbars
Remove custom orange thumb color from vertical scrollbar so both
vertical and horizontal scrollbars use the same default style.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* CAMEL-23512: Reorder Log tab footer to group navigation hints
Place PgUp/PgDn and Home/End next to each other in the Log tab
footer for consistent grouping of navigation keys.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
---
.../jbang/core/commands/tui/CamelCatalogTui.java | 153 ++---
.../dsl/jbang/core/commands/tui/CamelMonitor.java | 647 +++++++++++----------
.../dsl/jbang/core/commands/tui/TuiHelper.java | 5 +-
3 files changed, 405 insertions(+), 400 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelCatalogTui.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelCatalogTui.java
index cbd2e3022b0c..7e8aef4c3b3b 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelCatalogTui.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelCatalogTui.java
@@ -28,6 +28,7 @@ import dev.tamboui.style.Color;
import dev.tamboui.style.Overflow;
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;
@@ -37,6 +38,7 @@ import dev.tamboui.tui.event.KeyCode;
import dev.tamboui.tui.event.KeyEvent;
import dev.tamboui.widgets.block.Block;
import dev.tamboui.widgets.block.BorderType;
+import dev.tamboui.widgets.block.Borders;
import dev.tamboui.widgets.paragraph.Paragraph;
import dev.tamboui.widgets.table.Cell;
import dev.tamboui.widgets.table.Row;
@@ -267,7 +269,7 @@ public class CamelCatalogTui extends CamelCommand {
}
// Escape: clear filter first, then go back, then quit
- if (ke.isKey(KeyCode.ESCAPE)) {
+ if (ke.isCancel()) {
if (focus == FOCUS_OPTIONS && (!optionFilter.isEmpty() ||
optionFullText)) {
optionFilter.setLength(0);
optionFullText = false;
@@ -290,7 +292,7 @@ public class CamelCatalogTui extends CamelCommand {
}
// Backspace: delete from active filter
- if (ke.isKey(KeyCode.BACKSPACE)) {
+ if (ke.isDeleteBackward()) {
if (focus == FOCUS_LIST && !componentFilter.isEmpty()) {
componentFilter.deleteCharAt(componentFilter.length() - 1);
applyComponentFilter();
@@ -302,7 +304,7 @@ public class CamelCatalogTui extends CamelCommand {
}
// Panel switching — only when no active filter on current panel
- if (ke.isKey(KeyCode.TAB)) {
+ if (ke.isFocusNext()) {
if (focus == FOCUS_LIST) {
focus = FOCUS_OPTIONS;
} else {
@@ -311,19 +313,19 @@ public class CamelCatalogTui extends CamelCommand {
descriptionScroll = 0;
return true;
}
- if (ke.isKey(KeyCode.RIGHT) && focus == FOCUS_LIST) {
+ if (ke.isRight() && focus == FOCUS_LIST) {
focus = FOCUS_OPTIONS;
descriptionScroll = 0;
return true;
}
- if (ke.isKey(KeyCode.LEFT) && focus == FOCUS_OPTIONS) {
+ if (ke.isLeft() && focus == FOCUS_OPTIONS) {
focus = FOCUS_LIST;
descriptionScroll = 0;
return true;
}
// Enter drills into options
- if (ke.isKey(KeyCode.ENTER) && focus == FOCUS_LIST) {
+ if (ke.isConfirm() && focus == FOCUS_LIST) {
focus = FOCUS_OPTIONS;
descriptionScroll = 0;
return true;
@@ -350,11 +352,11 @@ public class CamelCatalogTui extends CamelCommand {
}
return true;
}
- if (ke.isKey(KeyCode.PAGE_UP)) {
+ if (ke.isPageUp()) {
descriptionScroll = Math.max(0, descriptionScroll - 5);
return true;
}
- if (ke.isKey(KeyCode.PAGE_DOWN)) {
+ if (ke.isPageDown()) {
descriptionScroll += 5;
return true;
}
@@ -374,10 +376,10 @@ public class CamelCatalogTui extends CamelCommand {
// Typing filters the active panel
if (ke.code() == KeyCode.CHAR) {
if (focus == FOCUS_LIST) {
- componentFilter.append(ke.character());
+ componentFilter.append(ke.string());
applyComponentFilter();
} else {
- optionFilter.append(ke.character());
+ optionFilter.append(ke.string());
applyOptionFilter();
}
return true;
@@ -410,10 +412,10 @@ public class CamelCatalogTui extends CamelCommand {
private void renderHeader(Frame frame, Rect area) {
Line titleLine = Line.from(
- Span.styled(" Camel Catalog",
Style.create().fg(Color.rgb(0xF6, 0x91, 0x23)).bold()),
+ Span.styled(" Camel Catalog", Style.EMPTY.fg(Color.rgb(0xF6,
0x91, 0x23)).bold()),
Span.raw(" "),
Span.styled(filteredComponents.size() + "/" +
allComponents.size() + " components",
- Style.create().fg(Color.CYAN)));
+ Style.EMPTY.fg(Color.CYAN)));
Block headerBlock = Block.builder()
.borderType(BorderType.ROUNDED)
@@ -439,8 +441,8 @@ public class CamelCatalogTui extends CamelCommand {
List<Row> rows = new ArrayList<>();
for (ComponentInfo comp : filteredComponents) {
Style nameStyle = comp.deprecated
- ? Style.create().fg(Color.RED).dim()
- : Style.create().fg(Color.CYAN);
+ ? Style.EMPTY.fg(Color.RED).dim()
+ : Style.EMPTY.fg(Color.CYAN);
String label = comp.name;
if (comp.deprecated) {
label = label + " (deprecated)";
@@ -449,12 +451,12 @@ public class CamelCatalogTui extends CamelCommand {
}
if (rows.isEmpty()) {
- rows.add(Row.from(Cell.from(Span.styled("No matching components",
Style.create().dim()))));
+ rows.add(Row.from(Cell.from(Span.styled("No matching components",
Style.EMPTY.dim()))));
}
Style borderStyle = focus == FOCUS_LIST
- ? Style.create().fg(Color.rgb(0xF6, 0x91, 0x23))
- : Style.create();
+ ? Style.EMPTY.fg(Color.rgb(0xF6, 0x91, 0x23))
+ : Style.EMPTY;
String modePrefix = componentFullText ? "/" : "";
String listTitle = componentFilter.isEmpty() && !componentFullText
@@ -464,7 +466,7 @@ public class CamelCatalogTui extends CamelCommand {
Table table = Table.builder()
.rows(rows)
.widths(Constraint.fill())
- .highlightStyle(Style.create().fg(Color.WHITE).bold().onBlue())
+ .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
.highlightSpacing(Table.HighlightSpacing.ALWAYS)
.block(Block.builder()
.borderType(BorderType.ROUNDED)
@@ -478,8 +480,8 @@ public class CamelCatalogTui extends CamelCommand {
private void renderOptionsTable(Frame frame, Rect area) {
Style borderStyle = focus == FOCUS_OPTIONS
- ? Style.create().fg(Color.rgb(0xF6, 0x91, 0x23))
- : Style.create();
+ ? Style.EMPTY.fg(Color.rgb(0xF6, 0x91, 0x23))
+ : Style.EMPTY;
String optModePrefix = optionFullText ? "/" : "";
String optTitle = optionFilter.isEmpty() && !optionFullText
@@ -491,7 +493,7 @@ public class CamelCatalogTui extends CamelCommand {
frame.renderWidget(
Paragraph.builder()
.text(Text.from(Line.from(
- Span.styled(emptyMsg,
Style.create().dim()))))
+ Span.styled(emptyMsg, Style.EMPTY.dim()))))
.block(Block.builder()
.borderType(BorderType.ROUNDED)
.borderStyle(borderStyle)
@@ -508,11 +510,11 @@ public class CamelCatalogTui extends CamelCommand {
}
Row header = Row.from(
- Cell.from(Span.styled("NAME", Style.create().bold())),
- Cell.from(Span.styled("TYPE", Style.create().bold())),
- Cell.from(Span.styled("REQ", Style.create().bold())),
- Cell.from(Span.styled("DEFAULT", Style.create().bold())),
- Cell.from(Span.styled("KIND", Style.create().bold())));
+ Cell.from(Span.styled("NAME", Style.EMPTY.bold())),
+ Cell.from(Span.styled("TYPE", Style.EMPTY.bold())),
+ Cell.from(Span.styled("REQ", Style.EMPTY.bold())),
+ Cell.from(Span.styled("DEFAULT", Style.EMPTY.bold())),
+ Cell.from(Span.styled("KIND", Style.EMPTY.bold())));
Table table = Table.builder()
.rows(rows)
@@ -523,7 +525,7 @@ public class CamelCatalogTui extends CamelCommand {
Constraint.length(4),
Constraint.length(12),
Constraint.fill())
- .highlightStyle(Style.create().fg(Color.WHITE).bold().onBlue())
+ .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
.highlightSpacing(Table.HighlightSpacing.ALWAYS)
.block(Block.builder()
.borderType(BorderType.ROUNDED)
@@ -536,9 +538,11 @@ public class CamelCatalogTui extends CamelCommand {
}
private void renderSeparator(Frame frame, Rect area) {
- String line = "\u2500".repeat(Math.max(0, area.width()));
frame.renderWidget(
- Paragraph.from(Line.from(Span.styled(line,
Style.create().fg(Color.DARK_GRAY)))),
+ Block.builder()
+ .borders(Borders.TOP_ONLY)
+ .borderStyle(Style.EMPTY.fg(Color.DARK_GRAY))
+ .build(),
area);
}
@@ -573,7 +577,9 @@ public class CamelCatalogTui extends CamelCommand {
flowFields(lines, fields, wrapWidth, opt);
lines.add(Line.from(Span.raw("")));
- wrapText(lines, opt.description, wrapWidth, Style.create());
+ if (opt.description != null && !opt.description.isEmpty()) {
+ lines.add(Line.from(Span.raw(" " + opt.description)));
+ }
} else {
title = " Description ";
}
@@ -598,32 +604,19 @@ public class CamelCatalogTui extends CamelCommand {
flowFields(lines, fields, wrapWidth, null);
lines.add(Line.from(Span.raw("")));
- wrapText(lines, comp.description, wrapWidth, Style.create());
+ if (comp.description != null && !comp.description.isEmpty()) {
+ lines.add(Line.from(Span.raw(" " + comp.description)));
+ }
} else {
title = " Description ";
}
}
- // Apply scroll
- int innerHeight = Math.max(1, area.height() - 2);
- int maxScroll = Math.max(0, lines.size() - innerHeight);
- if (descriptionScroll > maxScroll) {
- descriptionScroll = maxScroll;
- }
- List<Line> visible;
- if (descriptionScroll > 0) {
- int end = Math.min(descriptionScroll + innerHeight, lines.size());
- visible = lines.subList(descriptionScroll, end);
- } else if (lines.size() > innerHeight) {
- visible = lines.subList(0, innerHeight);
- } else {
- visible = lines;
- }
-
frame.renderWidget(
Paragraph.builder()
- .text(Text.from(visible))
- .overflow(Overflow.CLIP)
+ .text(Text.from(lines))
+ .overflow(Overflow.WRAP_WORD)
+ .scroll(descriptionScroll)
.block(Block.builder()
.borderType(BorderType.ROUNDED)
.title(title)
@@ -634,34 +627,34 @@ public class CamelCatalogTui extends CamelCommand {
private Row optionToRow(OptionInfo opt) {
Style nameStyle = opt.required
- ? Style.create().fg(Color.CYAN).bold()
- : Style.create().fg(Color.CYAN);
+ ? Style.EMPTY.fg(Color.CYAN).bold()
+ : Style.EMPTY.fg(Color.CYAN);
return Row.from(
Cell.from(Span.styled(opt.name, nameStyle)),
- Cell.from(Span.styled(opt.type, Style.create().dim())),
+ Cell.from(Span.styled(opt.type, Style.EMPTY.dim())),
Cell.from(opt.required
- ? Span.styled("*", Style.create().fg(Color.RED).bold())
+ ? Span.styled("*", Style.EMPTY.fg(Color.RED).bold())
: Span.raw("")),
- Cell.from(Span.styled(opt.defaultValue, Style.create().dim())),
- Cell.from(Span.styled(opt.kind != null ? opt.kind : "",
Style.create().dim())));
+ Cell.from(Span.styled(opt.defaultValue, Style.EMPTY.dim())),
+ Cell.from(Span.styled(opt.kind != null ? opt.kind : "",
Style.EMPTY.dim())));
}
private void renderFooter(Frame frame, Rect area) {
Line footer = Line.from(
- Span.styled(" Type", Style.create().fg(Color.YELLOW).bold()),
+ Span.styled(" Type", Style.EMPTY.fg(Color.YELLOW).bold()),
Span.raw(" name filter "),
- Span.styled("/", Style.create().fg(Color.YELLOW).bold()),
+ Span.styled("/", Style.EMPTY.fg(Color.YELLOW).bold()),
Span.raw(" full-text "),
- Span.styled("Esc", Style.create().fg(Color.YELLOW).bold()),
+ Span.styled("Esc", Style.EMPTY.fg(Color.YELLOW).bold()),
Span.raw(" clear/back/quit "),
- Span.styled("\u2191\u2193",
Style.create().fg(Color.YELLOW).bold()),
+ Span.styled("\u2191\u2193",
Style.EMPTY.fg(Color.YELLOW).bold()),
Span.raw(" navigate "),
- Span.styled("\u2190\u2192",
Style.create().fg(Color.YELLOW).bold()),
+ Span.styled("\u2190\u2192",
Style.EMPTY.fg(Color.YELLOW).bold()),
Span.raw("/"),
- Span.styled("Tab", Style.create().fg(Color.YELLOW).bold()),
+ Span.styled("Tab", Style.EMPTY.fg(Color.YELLOW).bold()),
Span.raw(" panels "),
- Span.styled("PgUp/Dn", Style.create().fg(Color.YELLOW).bold()),
+ Span.styled("PgUp/Dn", Style.EMPTY.fg(Color.YELLOW).bold()),
Span.raw(" scroll"));
frame.renderWidget(Paragraph.from(footer), area);
@@ -677,10 +670,10 @@ public class CamelCatalogTui extends CamelCommand {
for (String[] field : fields) {
String label = field[0] + ": ";
String value = field[1];
- int fieldLen = label.length() + value.length();
+ int fieldLen = CharWidth.of(label) + CharWidth.of(value);
// If adding this field would exceed width, flush current line
- if (!currentSpans.isEmpty() && currentLen + gap.length() +
fieldLen > maxWidth) {
+ if (!currentSpans.isEmpty() && currentLen + CharWidth.of(gap) +
fieldLen > maxWidth) {
lines.add(Line.from(currentSpans));
currentSpans = new ArrayList<>();
currentLen = 1;
@@ -688,25 +681,25 @@ public class CamelCatalogTui extends CamelCommand {
if (!currentSpans.isEmpty()) {
currentSpans.add(Span.raw(gap));
- currentLen += gap.length();
+ currentLen += CharWidth.of(gap);
} else {
currentSpans.add(Span.raw(" "));
}
- currentSpans.add(Span.styled(label,
Style.create().fg(Color.YELLOW).bold()));
+ currentSpans.add(Span.styled(label,
Style.EMPTY.fg(Color.YELLOW).bold()));
// Apply special styling for certain values
Style valueStyle;
if ("DEPRECATED".equals(value)) {
- valueStyle = Style.create().fg(Color.RED).bold();
+ valueStyle = Style.EMPTY.fg(Color.RED).bold();
} else if (opt != null && "Required".equals(field[0]) &&
opt.required) {
- valueStyle = Style.create().fg(Color.RED).bold();
+ valueStyle = Style.EMPTY.fg(Color.RED).bold();
} else if (opt != null && "Name".equals(field[0])) {
- valueStyle = Style.create().fg(Color.CYAN).bold();
+ valueStyle = Style.EMPTY.fg(Color.CYAN).bold();
} else if ("Label".equals(field[0]) || "Values".equals(field[0])) {
- valueStyle = Style.create().fg(Color.CYAN);
+ valueStyle = Style.EMPTY.fg(Color.CYAN);
} else {
- valueStyle = Style.create();
+ valueStyle = Style.EMPTY;
}
currentSpans.add(Span.styled(value, valueStyle));
currentLen += fieldLen;
@@ -717,24 +710,6 @@ public class CamelCatalogTui extends CamelCommand {
}
}
- private static void wrapText(List<Line> lines, String text, int width,
Style style) {
- if (text == null || text.isEmpty()) {
- return;
- }
- int pos = 0;
- while (pos < text.length()) {
- int end = Math.min(pos + width, text.length());
- if (end < text.length() && end > pos) {
- int lastSpace = text.lastIndexOf(' ', end);
- if (lastSpace > pos) {
- end = lastSpace + 1;
- }
- }
- lines.add(Line.from(Span.styled(" " + text.substring(pos,
end).trim(), style)));
- pos = end;
- }
- }
-
// ---- Data Classes ----
static class ComponentInfo {
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 8b3f9e9a3fc4..0984a3e65fff 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
@@ -32,6 +32,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -49,12 +50,13 @@ import dev.tamboui.style.Color;
import dev.tamboui.style.Overflow;
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;
+import dev.tamboui.tui.TuiConfig;
import dev.tamboui.tui.TuiRunner;
import dev.tamboui.tui.event.Event;
-import dev.tamboui.tui.event.KeyCode;
import dev.tamboui.tui.event.KeyEvent;
import dev.tamboui.tui.event.MouseEvent;
import dev.tamboui.tui.event.MouseEventKind;
@@ -131,6 +133,8 @@ public class CamelMonitor extends CamelCommand {
private final TableState routeTableState = new TableState();
private final TableState healthTableState = new TableState();
private final TableState endpointTableState = new TableState();
+ private final TableState processorTableState = new TableState();
+ private final TableState routeHeaderTableState = new TableState();
private final TabsState tabsState = new TabsState(TAB_OVERVIEW);
// Sparkline: throughput history per PID (one point per second)
@@ -148,8 +152,8 @@ public class CamelMonitor extends CamelCommand {
private boolean showOnlyDown;
// Log state
- private final List<String> logLines = new ArrayList<>();
- private final List<LogEntry> filteredLogEntries = new ArrayList<>();
+ private List<String> logLines = new ArrayList<>();
+ private volatile List<LogEntry> filteredLogEntries = new ArrayList<>();
private final TableState logTableState = new TableState();
private boolean logFollowMode = true;
private boolean showLogTrace = true;
@@ -187,6 +191,9 @@ public class CamelMonitor extends CamelCommand {
private int diagramCropH = -1;
private volatile long lastRefresh;
+ private final AtomicBoolean refreshInProgress = new AtomicBoolean(false);
+ private final AtomicBoolean diagramLoading = new AtomicBoolean(false);
+ private TuiRunner runner;
private ClassLoader classLoader;
@@ -203,16 +210,19 @@ public class CamelMonitor extends CamelCommand {
Thread.currentThread().setContextClassLoader(classLoader);
TuiHelper.preloadClasses(classLoader);
- // Initial data load
- refreshData();
+ // Initial data load (synchronous before TUI starts)
+ refreshDataSync();
- try (var tui = TuiRunner.create()) {
+ try (var tui =
TuiRunner.create(TuiConfig.builder().mouseCapture(true).build())) {
+ this.runner = tui;
// Intercept Ctrl+C: quit the TUI cleanly instead of letting
// the JVM tear down the classloader while we're still running
Signal.handle(new Signal("INT"), sig -> tui.quit());
tui.run(
this::handleEvent,
this::render);
+ } finally {
+ this.runner = null;
}
return 0;
}
@@ -234,7 +244,7 @@ public class CamelMonitor extends CamelCommand {
}
if (event instanceof KeyEvent ke) {
// Global keys
- if (ke.isQuit() || ke.isCharIgnoreCase('q') ||
ke.isKey(KeyCode.ESCAPE)) {
+ if (ke.isQuit() || ke.isCharIgnoreCase('q') || ke.isCancel()) {
if (showDiagram) {
showDiagram = false;
diagramImageData = null;
@@ -276,7 +286,7 @@ public class CamelMonitor extends CamelCommand {
}
// Tab cycling
- if (ke.isKey(KeyCode.TAB)) {
+ if (ke.isFocusNext()) {
int next = (tabsState.selected() + 1) % NUM_TABS;
if (next != TAB_OVERVIEW) {
selectCurrentIntegration();
@@ -305,7 +315,7 @@ public class CamelMonitor extends CamelCommand {
}
return true;
}
- if (ke.isKey(KeyCode.PAGE_UP)) {
+ if (ke.isPageUp()) {
if (showDiagram && tab == TAB_ROUTES) {
diagramScroll = Math.max(0, diagramScroll - 20);
} else if (tab == TAB_LOG) {
@@ -316,7 +326,7 @@ public class CamelMonitor extends CamelCommand {
}
return true;
}
- if (ke.isKey(KeyCode.PAGE_DOWN)) {
+ if (ke.isPageDown()) {
if (showDiagram && tab == TAB_ROUTES) {
diagramScroll += 20;
} else if (tab == TAB_LOG) {
@@ -338,14 +348,14 @@ public class CamelMonitor extends CamelCommand {
return true;
}
}
- if (ke.isKey(KeyCode.HOME)) {
+ if (ke.isHome()) {
if (showDiagram && tab == TAB_ROUTES) {
diagramScroll = 0;
diagramScrollX = 0;
return true;
}
}
- if (ke.isKey(KeyCode.END)) {
+ if (ke.isEnd()) {
if (showDiagram && tab == TAB_ROUTES) {
diagramScroll = Integer.MAX_VALUE;
return true;
@@ -353,7 +363,7 @@ public class CamelMonitor extends CamelCommand {
}
// Enter to drill into selected integration
- if (ke.isKey(KeyCode.ENTER) && tab == TAB_OVERVIEW) {
+ if (ke.isConfirm() && tab == TAB_OVERVIEW) {
selectCurrentIntegration();
if (selectedPid != null) {
tabsState.select(TAB_ROUTES);
@@ -400,39 +410,39 @@ public class CamelMonitor extends CamelCommand {
if (tab == TAB_LOG) {
if (ke.isCharIgnoreCase('t')) {
showLogTrace = !showLogTrace;
- applyLogFilters();
+ filteredLogEntries = applyLogFilters(logLines);
return true;
}
if (ke.isCharIgnoreCase('d')) {
showLogDebug = !showLogDebug;
- applyLogFilters();
+ filteredLogEntries = applyLogFilters(logLines);
return true;
}
if (ke.isCharIgnoreCase('i')) {
showLogInfo = !showLogInfo;
- applyLogFilters();
+ filteredLogEntries = applyLogFilters(logLines);
return true;
}
if (ke.isCharIgnoreCase('w')) {
showLogWarn = !showLogWarn;
- applyLogFilters();
+ filteredLogEntries = applyLogFilters(logLines);
return true;
}
if (ke.isCharIgnoreCase('e')) {
showLogError = !showLogError;
- applyLogFilters();
+ filteredLogEntries = applyLogFilters(logLines);
return true;
}
if (ke.isCharIgnoreCase('f')) {
logFollowMode = !logFollowMode;
return true;
}
- if (ke.isChar('g')) {
+ if (ke.isHome()) {
logFollowMode = false;
logTableState.select(0);
return true;
}
- if (ke.isChar('G')) {
+ if (ke.isEnd()) {
logFollowMode = true;
return true;
}
@@ -563,11 +573,11 @@ public class CamelMonitor extends CamelCommand {
long activeCount = infos.stream().filter(i -> !i.vanishing).count();
Line titleLine = Line.from(
- Span.styled(" Camel Monitor",
Style.create().fg(Color.rgb(0xF6, 0x91, 0x23)).bold()),
+ Span.styled(" Camel Monitor", Style.EMPTY.fg(Color.rgb(0xF6,
0x91, 0x23)).bold()),
Span.raw(" "),
- Span.styled(camelVersion != null ? "v" + camelVersion : "",
Style.create().fg(Color.GREEN)),
+ Span.styled(camelVersion != null ? "v" + camelVersion : "",
Style.EMPTY.fg(Color.GREEN)),
Span.raw(" "),
- Span.styled(activeCount + " integration(s)",
Style.create().fg(Color.CYAN)));
+ Span.styled(activeCount + " integration(s)",
Style.EMPTY.fg(Color.CYAN)));
Block headerBlock = Block.builder()
.borderType(BorderType.ROUNDED)
@@ -589,8 +599,8 @@ public class CamelMonitor extends CamelCommand {
" 4 Endpoints" + sel + " ",
" 5 Log" + sel + " ",
" 6 Trace" + sel + " ")
- .highlightStyle(Style.create().fg(Color.rgb(0xF6, 0x91,
0x23)).bold())
- .divider(Span.styled(" | ", Style.create().dim()))
+ .highlightStyle(Style.EMPTY.fg(Color.rgb(0xF6, 0x91,
0x23)).bold())
+ .divider(Span.styled(" | ", Style.EMPTY.dim()))
.build();
frame.renderStatefulWidget(tabs, area, tabsState);
@@ -630,7 +640,7 @@ public class CamelMonitor extends CamelCommand {
long elapsed = System.currentTimeMillis() - info.vanishStart;
float fade = 1.0f - Math.min(1.0f, (float) elapsed /
VANISH_DURATION_MS);
int gray = (int) (100 * fade);
- Style dimStyle = Style.create().fg(Color.indexed(232 +
Math.min(gray / 4, 23)));
+ Style dimStyle = Style.EMPTY.fg(Color.indexed(232 +
Math.min(gray / 4, 23)));
rows.add(Row.from(
Cell.from(Span.styled(info.pid, dimStyle)),
@@ -638,7 +648,7 @@ public class CamelMonitor extends CamelCommand {
Cell.from(Span.styled("", dimStyle)),
Cell.from(Span.styled("", dimStyle)),
Cell.from(Span.styled("", dimStyle)),
- Cell.from(Span.styled("\u2716 Stopped",
Style.create().fg(Color.RED).dim())),
+ Cell.from(Span.styled("\u2716 Stopped",
Style.EMPTY.fg(Color.RED).dim())),
Cell.from(Span.styled("", dimStyle)),
Cell.from(Span.styled(info.ago != null ? info.ago :
"", dimStyle)),
Cell.from(Span.styled("", dimStyle)),
@@ -652,9 +662,9 @@ public class CamelMonitor extends CamelCommand {
Cell.from(Span.styled("", dimStyle))));
} else {
Style statusStyle = switch (extractState(info.state)) {
- case "Started" -> Style.create().fg(Color.GREEN);
- case "Stopped" -> Style.create().fg(Color.RED);
- default -> Style.create().fg(Color.YELLOW);
+ case "Started" -> Style.EMPTY.fg(Color.GREEN);
+ case "Stopped" -> Style.EMPTY.fg(Color.RED);
+ default -> Style.EMPTY.fg(Color.YELLOW);
};
String platformDisplay = info.platform != null ? info.platform
: "";
@@ -662,13 +672,13 @@ public class CamelMonitor extends CamelCommand {
platformDisplay += " " + info.platformVersion;
}
- Style failStyle = info.failed > 0 ?
Style.create().fg(Color.RED).bold() : Style.create();
+ Style failStyle = info.failed > 0 ?
Style.EMPTY.fg(Color.RED).bold() : Style.EMPTY;
String sinceLastDisplay = formatSinceLast(info);
rows.add(Row.from(
Cell.from(info.pid),
- Cell.from(Span.styled(info.name != null ? info.name :
"", Style.create().fg(Color.CYAN))),
+ Cell.from(Span.styled(info.name != null ? info.name :
"", Style.EMPTY.fg(Color.CYAN))),
Cell.from(info.camelVersion != null ?
info.camelVersion : ""),
Cell.from(platformDisplay),
Cell.from(info.ready != null ? info.ready : ""),
@@ -688,23 +698,23 @@ public class CamelMonitor extends CamelCommand {
}
Row header = Row.from(
- Cell.from(Span.styled("PID", Style.create().bold())),
- Cell.from(Span.styled("NAME", Style.create().bold())),
- Cell.from(Span.styled("CAMEL", Style.create().bold())),
- Cell.from(Span.styled("PLATFORM", Style.create().bold())),
- Cell.from(Span.styled("READY", Style.create().bold())),
- Cell.from(Span.styled("STATUS", Style.create().bold())),
- Cell.from(Span.styled("RELOAD", Style.create().bold())),
- Cell.from(Span.styled("AGE", Style.create().bold())),
- Cell.from(Span.styled("ROUTE", Style.create().bold())),
- Cell.from(Span.styled("MSG/S", Style.create().bold())),
- Cell.from(Span.styled("TOTAL", Style.create().bold())),
- Cell.from(Span.styled("FAIL", Style.create().bold())),
- Cell.from(Span.styled("INFLIGHT", Style.create().bold())),
- Cell.from(Span.styled("LAST", Style.create().bold())),
- Cell.from(Span.styled("SINCE-LAST", Style.create().bold())),
- Cell.from(Span.styled("HEAP", Style.create().bold())),
- Cell.from(Span.styled("THREADS", Style.create().bold())));
+ Cell.from(Span.styled("PID", Style.EMPTY.bold())),
+ Cell.from(Span.styled("NAME", Style.EMPTY.bold())),
+ Cell.from(Span.styled("CAMEL", Style.EMPTY.bold())),
+ Cell.from(Span.styled("PLATFORM", Style.EMPTY.bold())),
+ Cell.from(Span.styled("READY", Style.EMPTY.bold())),
+ Cell.from(Span.styled("STATUS", Style.EMPTY.bold())),
+ Cell.from(Span.styled("RELOAD", Style.EMPTY.bold())),
+ Cell.from(Span.styled("AGE", Style.EMPTY.bold())),
+ Cell.from(Span.styled("ROUTE", Style.EMPTY.bold())),
+ Cell.from(Span.styled("MSG/S", Style.EMPTY.bold())),
+ Cell.from(Span.styled("TOTAL", Style.EMPTY.bold())),
+ Cell.from(Span.styled("FAIL", Style.EMPTY.bold())),
+ Cell.from(Span.styled("INFLIGHT", Style.EMPTY.bold())),
+ Cell.from(Span.styled("LAST", Style.EMPTY.bold())),
+ Cell.from(Span.styled("SINCE-LAST", Style.EMPTY.bold())),
+ Cell.from(Span.styled("HEAP", Style.EMPTY.bold())),
+ Cell.from(Span.styled("THREADS", Style.EMPTY.bold())));
Table table = Table.builder()
.rows(rows)
@@ -727,7 +737,7 @@ public class CamelMonitor extends CamelCommand {
Constraint.length(12),
Constraint.length(14),
Constraint.length(10))
- .highlightStyle(Style.create().fg(Color.WHITE).bold().onBlue())
+ .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
.highlightSpacing(Table.HighlightSpacing.ALWAYS)
.block(Block.builder().borderType(BorderType.ROUNDED).title("
Integrations ").build())
.build();
@@ -763,7 +773,7 @@ public class CamelMonitor extends CamelCommand {
.max(maxTp > 0 ? maxTp + 2 : 2)
.barWidth(1)
.barGap(0)
- .barStyle(Style.create().fg(Color.GREEN))
+ .barStyle(Style.EMPTY.fg(Color.GREEN))
.block(Block.builder().borderType(BorderType.ROUNDED).title(chartTitle).build())
.build();
@@ -804,15 +814,15 @@ public class CamelMonitor extends CamelCommand {
List<Row> routeRows = new ArrayList<>();
for (RouteInfo route : sortedRoutes) {
Style stateStyle = "Started".equals(route.state)
- ? Style.create().fg(Color.GREEN)
- : Style.create().fg(Color.RED);
+ ? Style.EMPTY.fg(Color.GREEN)
+ : Style.EMPTY.fg(Color.RED);
Style failStyle = route.failed > 0
- ? Style.create().fg(Color.RED).bold()
- : Style.create();
+ ? Style.EMPTY.fg(Color.RED).bold()
+ : Style.EMPTY;
routeRows.add(Row.from(
- Cell.from(Span.styled(route.routeId != null ?
route.routeId : "", Style.create().fg(Color.CYAN))),
+ Cell.from(Span.styled(route.routeId != null ?
route.routeId : "", Style.EMPTY.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 : ""),
@@ -825,11 +835,11 @@ public class CamelMonitor extends CamelCommand {
Table routeTable = Table.builder()
.rows(routeRows)
.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("ROUTE", Style.EMPTY.bold())),
+ Cell.from(Span.styled("FROM", Style.EMPTY.bold())),
+ Cell.from(Span.styled("STATE", Style.EMPTY.bold())),
+ Cell.from(Span.styled("UPTIME", Style.EMPTY.bold())),
+ Cell.from(Span.styled("THRUPUT", Style.EMPTY.bold())),
Cell.from(Span.styled(routeSortLabel("TOTAL",
"total"), routeSortStyle("total"))),
Cell.from(Span.styled(routeSortLabel("FAILED",
"failed"), routeSortStyle("failed"))),
Cell.from(Span.styled(routeSortLabel("MEAN/MAX",
"mean"), routeSortStyle("mean")))))
@@ -842,7 +852,7 @@ public class CamelMonitor extends CamelCommand {
Constraint.length(8),
Constraint.length(8),
Constraint.length(12))
- .highlightStyle(Style.create().fg(Color.WHITE).bold().onBlue())
+ .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
.highlightSpacing(Table.HighlightSpacing.ALWAYS)
.block(Block.builder().borderType(BorderType.ROUNDED)
.title(" Routes [" + info.name + "] sort:" + routeSort
+ " ").build())
@@ -863,7 +873,7 @@ public class CamelMonitor extends CamelCommand {
} else {
frame.renderWidget(
Paragraph.builder()
- .text(Text.from(Line.from(Span.styled("No
routes", Style.create().dim()))))
+ .text(Text.from(Line.from(Span.styled("No
routes", Style.EMPTY.dim()))))
.block(Block.builder().borderType(BorderType.ROUNDED).title(" Processors
").build())
.build(),
chunks.get(1));
@@ -892,22 +902,22 @@ public class CamelMonitor extends CamelCommand {
private Style routeSortStyle(String column) {
return routeSort.equals(column)
- ? Style.create().fg(Color.YELLOW).bold()
- : Style.create().bold();
+ ? Style.EMPTY.fg(Color.YELLOW).bold()
+ : Style.EMPTY.bold();
}
private void renderProcessors(Frame frame, Rect area, RouteInfo route) {
List<Row> rows = new ArrayList<>();
for (ProcessorInfo proc : route.processors) {
String indent = " ".repeat(proc.level);
- Style nameStyle = proc.failed > 0 ? Style.create().fg(Color.RED) :
Style.create().fg(Color.CYAN);
+ Style nameStyle = proc.failed > 0 ? Style.EMPTY.fg(Color.RED) :
Style.EMPTY.fg(Color.CYAN);
rows.add(Row.from(
Cell.from(Span.styled(indent + (proc.id != null ? proc.id
: ""), nameStyle)),
Cell.from(proc.processor != null ? proc.processor : ""),
Cell.from(String.valueOf(proc.total)),
Cell.from(proc.failed > 0
- ? Span.styled(String.valueOf(proc.failed),
Style.create().fg(Color.RED))
+ ? Span.styled(String.valueOf(proc.failed),
Style.EMPTY.fg(Color.RED))
: Span.raw("0")),
Cell.from(proc.meanTime + "ms"),
Cell.from(proc.maxTime + "ms"),
@@ -917,13 +927,13 @@ public class CamelMonitor extends CamelCommand {
Table table = Table.builder()
.rows(rows)
.header(Row.from(
- Cell.from(Span.styled("PROCESSOR",
Style.create().bold())),
- Cell.from(Span.styled("TYPE", Style.create().bold())),
- Cell.from(Span.styled("TOTAL", Style.create().bold())),
- Cell.from(Span.styled("FAILED",
Style.create().bold())),
- Cell.from(Span.styled("MEAN", Style.create().bold())),
- Cell.from(Span.styled("MAX", Style.create().bold())),
- Cell.from(Span.styled("LAST", Style.create().bold()))))
+ Cell.from(Span.styled("PROCESSOR",
Style.EMPTY.bold())),
+ Cell.from(Span.styled("TYPE", Style.EMPTY.bold())),
+ Cell.from(Span.styled("TOTAL", Style.EMPTY.bold())),
+ Cell.from(Span.styled("FAILED", Style.EMPTY.bold())),
+ Cell.from(Span.styled("MEAN", Style.EMPTY.bold())),
+ Cell.from(Span.styled("MAX", Style.EMPTY.bold())),
+ Cell.from(Span.styled("LAST", Style.EMPTY.bold()))))
.widths(
Constraint.fill(),
Constraint.length(15),
@@ -936,7 +946,7 @@ public class CamelMonitor extends CamelCommand {
.title(" Processors [" + route.routeId + "] ").build())
.build();
- frame.renderStatefulWidget(table, area, new TableState());
+ frame.renderStatefulWidget(table, area, processorTableState);
}
private void renderRouteHeader(Frame frame, Rect area, IntegrationInfo
info) {
@@ -953,13 +963,13 @@ public class CamelMonitor extends CamelCommand {
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.EMPTY.fg(Color.GREEN)
+ : Style.EMPTY.fg(Color.RED);
Style failStyle = route.failed > 0
- ? Style.create().fg(Color.RED).bold()
- : Style.create();
+ ? Style.EMPTY.fg(Color.RED).bold()
+ : Style.EMPTY;
rows.add(Row.from(
- Cell.from(Span.styled(route.routeId != null ?
route.routeId : "", Style.create().fg(Color.CYAN))),
+ Cell.from(Span.styled(route.routeId != null ?
route.routeId : "", Style.EMPTY.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 : ""),
@@ -972,14 +982,14 @@ public class CamelMonitor extends CamelCommand {
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()))))
+ Cell.from(Span.styled("ROUTE", Style.EMPTY.bold())),
+ Cell.from(Span.styled("FROM", Style.EMPTY.bold())),
+ Cell.from(Span.styled("STATE", Style.EMPTY.bold())),
+ Cell.from(Span.styled("UPTIME", Style.EMPTY.bold())),
+ Cell.from(Span.styled("THRUPUT", Style.EMPTY.bold())),
+ Cell.from(Span.styled("TOTAL", Style.EMPTY.bold())),
+ Cell.from(Span.styled("FAILED", Style.EMPTY.bold())),
+ Cell.from(Span.styled("MEAN/MAX",
Style.EMPTY.bold()))))
.widths(
Constraint.length(12),
Constraint.fill(),
@@ -989,12 +999,12 @@ public class CamelMonitor extends CamelCommand {
Constraint.length(8),
Constraint.length(8),
Constraint.length(12))
- .highlightStyle(Style.create().fg(Color.WHITE).bold().onBlue())
+ .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
.block(Block.builder().borderType(BorderType.ROUNDED)
.title(" Route [" + info.name + "] ").build())
.build();
- frame.renderStatefulWidget(table, area, new TableState());
+ frame.renderStatefulWidget(table, area, routeHeaderTableState);
}
private void renderDiagram(Frame frame, Rect area) {
@@ -1011,7 +1021,7 @@ public class CamelMonitor extends CamelCommand {
// Compute max width for horizontal scrolling
int maxWidth = 0;
for (String line : diagramLines) {
- maxWidth = Math.max(maxWidth, line.length());
+ maxWidth = Math.max(maxWidth, CharWidth.of(line));
}
Rect inner = block.inner(area);
@@ -1059,9 +1069,7 @@ public class CamelMonitor extends CamelCommand {
diagramVScrollState.viewportContentLength(visibleLines);
diagramVScrollState.position(diagramScroll);
frame.renderStatefulWidget(
- Scrollbar.builder()
- .thumbStyle(Style.create().fg(Color.rgb(0xF6, 0x91,
0x23)))
- .build(),
+ Scrollbar.builder().build(),
hChunks.get(1), diagramVScrollState);
// Render horizontal scrollbar
@@ -1140,9 +1148,7 @@ public class CamelMonitor extends CamelCommand {
diagramVScrollState.viewportContentLength(viewRows);
diagramVScrollState.position(diagramScroll);
frame.renderStatefulWidget(
- Scrollbar.builder()
- .thumbStyle(Style.create().fg(Color.rgb(0xF6, 0x91,
0x23)))
- .build(),
+ Scrollbar.builder().build(),
hChunks.get(1), diagramVScrollState);
// Render horizontal scrollbar
@@ -1163,27 +1169,27 @@ public class CamelMonitor extends CamelCommand {
while (idx < text.length()) {
int open = text.indexOf('[', idx);
if (open < 0) {
- spans.add(Span.styled(text.substring(idx),
Style.create().fg(Color.WHITE)));
+ spans.add(Span.styled(text.substring(idx),
Style.EMPTY.fg(Color.WHITE)));
break;
}
int close = text.indexOf(']', open);
if (close < 0) {
- spans.add(Span.styled(text.substring(idx),
Style.create().fg(Color.WHITE)));
+ spans.add(Span.styled(text.substring(idx),
Style.EMPTY.fg(Color.WHITE)));
break;
}
if (open > idx) {
- spans.add(Span.styled(text.substring(idx, open),
Style.create().fg(Color.GRAY)));
+ spans.add(Span.styled(text.substring(idx, open),
Style.EMPTY.fg(Color.GRAY)));
}
String tag = text.substring(open + 1, close);
Color tagColor = getDiagramNodeColor(tag);
- spans.add(Span.styled("[" + tag + "]",
Style.create().fg(tagColor).bold()));
+ spans.add(Span.styled("[" + tag + "]",
Style.EMPTY.fg(tagColor).bold()));
int afterTag = close + 1;
int nextOpen = text.indexOf('[', afterTag);
int nextNewline = text.length();
int labelEnd = nextOpen >= 0 ? nextOpen : nextNewline;
if (afterTag < labelEnd) {
- spans.add(Span.styled(text.substring(afterTag, labelEnd),
Style.create().fg(Color.WHITE)));
+ spans.add(Span.styled(text.substring(afterTag, labelEnd),
Style.EMPTY.fg(Color.WHITE)));
}
idx = labelEnd;
}
@@ -1213,12 +1219,16 @@ public class CamelMonitor extends CamelCommand {
}
private void loadDiagramForSelectedRoute() {
- if (selectedPid == null) {
+ if (selectedPid == null || runner == null) {
+ return;
+ }
+ if (!diagramLoading.compareAndSet(false, true)) {
return;
}
IntegrationInfo info = findSelectedIntegration();
if (info == null || info.routes.isEmpty()) {
+ diagramLoading.set(false);
return;
}
@@ -1233,7 +1243,31 @@ public class CamelMonitor extends CamelCommand {
selectedRoute = sortedRoutes.get(0);
}
- Path outputFile = getOutputFile(selectedPid);
+ // Capture state needed by the background thread
+ String pid = selectedPid;
+ boolean textMode = diagramTextMode;
+ String routeId = selectedRoute.routeId;
+
+ // Show loading state immediately
+ diagramRouteId = routeId;
+ diagramLines = List.of("(Loading diagram...)");
+ diagramImageData = null;
+ diagramFullImageData = null;
+ showDiagram = true;
+ diagramScroll = 0;
+ diagramScrollX = 0;
+
+ runner.scheduler().execute(() -> {
+ try {
+ loadDiagramInBackground(pid, textMode, routeId);
+ } finally {
+ diagramLoading.set(false);
+ }
+ });
+ }
+
+ private void loadDiagramInBackground(String pid, boolean textMode, String
routeId) {
+ Path outputFile = getOutputFile(pid);
PathUtils.deleteFile(outputFile);
JsonObject root = new JsonObject();
@@ -1242,38 +1276,32 @@ public class CamelMonitor extends CamelCommand {
root.put("brief", false);
root.put("metric", true);
- Path actionFile = getActionFile(selectedPid);
+ Path actionFile = getActionFile(pid);
org.apache.camel.dsl.jbang.core.common.PathUtils.writeTextSafely(root.toJson(),
actionFile);
JsonObject jo = pollJsonResponse(outputFile, 5000);
PathUtils.deleteFile(outputFile);
if (jo == null) {
- diagramLines = List.of("(No response from integration)");
- diagramRouteId = selectedRoute.routeId;
- showDiagram = true;
- diagramScroll = 0;
+ applyDiagramResult(routeId, List.of("(No response from
integration)"), null, null, null);
return;
}
JsonArray arr = (JsonArray) jo.get("routes");
if (arr == null) {
- diagramLines = List.of("(No routes in response)");
- diagramRouteId = selectedRoute.routeId;
- showDiagram = true;
- diagramScroll = 0;
+ applyDiagramResult(routeId, List.of("(No routes in response)"),
null, null, null);
return;
}
List<RouteDiagramLayoutEngine.RouteInfo> diagramRoutes = new
ArrayList<>();
for (int i = 0; i < arr.size(); i++) {
JsonObject o = (JsonObject) arr.get(i);
- String routeId = objToString(o.get("routeId"));
- if (selectedRoute.routeId != null &&
!selectedRoute.routeId.equals(routeId)) {
+ String rid = objToString(o.get("routeId"));
+ if (routeId != null && !routeId.equals(rid)) {
continue;
}
RouteDiagramLayoutEngine.RouteInfo route = new
RouteDiagramLayoutEngine.RouteInfo();
- route.routeId = routeId;
+ route.routeId = rid;
List<JsonObject> lines = o.getCollection("code");
if (lines != null) {
for (JsonObject line : lines) {
@@ -1288,28 +1316,15 @@ public class CamelMonitor extends CamelCommand {
diagramRoutes.add(route);
}
- diagramRouteId = selectedRoute.routeId;
- diagramScroll = 0;
- diagramScrollX = 0;
- diagramCropX = -1;
- diagramCropY = -1;
- diagramCropW = -1;
- diagramCropH = -1;
-
- if (diagramTextMode) {
- diagramImageData = null;
- diagramFullImageData = null;
- diagramProtocol = null;
-
+ if (textMode) {
String ascii = renderAscii(diagramRoutes,
RouteDiagramLayoutEngine.DEFAULT_BOX_WIDTH, "CODE", true);
-
List<String> result = new ArrayList<>();
for (String line : ascii.split("\n", -1)) {
if (!line.isEmpty()) {
result.add(line);
}
}
- diagramLines = result;
+ applyDiagramResult(routeId, result, null, null, null);
} else {
TerminalImageCapabilities caps =
TerminalImageCapabilities.detect();
if (caps.supportsNativeImages()) {
@@ -1325,21 +1340,36 @@ public class CamelMonitor extends CamelCommand {
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();
+ ImageData resized = fullImage.resize(fullImage.width() / 2,
fullImage.height() / 2);
+ ImageProtocol protocol = caps.bestProtocol();
+ applyDiagramResult(routeId, Collections.emptyList(), resized,
resized, protocol);
} else {
- diagramImageData = null;
- diagramFullImageData = null;
- diagramProtocol = null;
- diagramLines = List.of(
+ applyDiagramResult(routeId, List.of(
"(Terminal does not support image rendering)",
- "(Press Shift+D for text diagram)");
+ "(Press Shift+D for text diagram)"), null, null, null);
}
}
+ }
- showDiagram = true;
+ private void applyDiagramResult(
+ String routeId, List<String> lines, ImageData imageData, ImageData
fullImageData, ImageProtocol protocol) {
+ if (runner == null) {
+ return;
+ }
+ runner.runOnRenderThread(() -> {
+ diagramRouteId = routeId;
+ diagramLines = lines;
+ diagramImageData = imageData;
+ diagramFullImageData = fullImageData;
+ diagramProtocol = protocol;
+ diagramScroll = 0;
+ diagramScrollX = 0;
+ diagramCropX = -1;
+ diagramCropY = -1;
+ diagramCropW = -1;
+ diagramCropH = -1;
+ showDiagram = true;
+ });
}
private static String renderAscii(
@@ -1401,13 +1431,13 @@ public class CamelMonitor extends CamelCommand {
Style stateStyle;
String icon;
if ("UP".equals(hc.state)) {
- stateStyle = Style.create().fg(Color.GREEN);
+ stateStyle = Style.EMPTY.fg(Color.GREEN);
icon = "\u2714 ";
} else if ("DOWN".equals(hc.state)) {
- stateStyle = Style.create().fg(Color.RED);
+ stateStyle = Style.EMPTY.fg(Color.RED);
icon = "\u2716 ";
} else {
- stateStyle = Style.create().fg(Color.YELLOW);
+ stateStyle = Style.EMPTY.fg(Color.YELLOW);
icon = "\u26A0 ";
}
@@ -1420,8 +1450,8 @@ public class CamelMonitor extends CamelCommand {
}
rows.add(Row.from(
- Cell.from(Span.styled(hc.group != null ? hc.group : "",
Style.create().dim())),
- Cell.from(Span.styled(hc.name != null ? hc.name : "",
Style.create().fg(Color.CYAN))),
+ Cell.from(Span.styled(hc.group != null ? hc.group : "",
Style.EMPTY.dim())),
+ Cell.from(Span.styled(hc.name != null ? hc.name : "",
Style.EMPTY.fg(Color.CYAN))),
Cell.from(Span.styled(icon + hc.state, stateStyle)),
Cell.from(kind),
Cell.from(hc.message != null ? hc.message : "")));
@@ -1431,7 +1461,7 @@ public class CamelMonitor extends CamelCommand {
rows.add(Row.from(
Cell.from(""),
Cell.from(Span.styled(showOnlyDown ? "No DOWN checks" :
"No health checks registered",
- Style.create().dim())),
+ Style.EMPTY.dim())),
Cell.from(""),
Cell.from(""),
Cell.from("")));
@@ -1444,18 +1474,18 @@ public class CamelMonitor extends CamelCommand {
Table table = Table.builder()
.rows(rows)
.header(Row.from(
- Cell.from(Span.styled("GROUP", Style.create().bold())),
- Cell.from(Span.styled("NAME", Style.create().bold())),
- Cell.from(Span.styled("STATUS",
Style.create().bold())),
- Cell.from(Span.styled("KIND", Style.create().bold())),
- Cell.from(Span.styled("MESSAGE",
Style.create().bold()))))
+ Cell.from(Span.styled("GROUP", Style.EMPTY.bold())),
+ Cell.from(Span.styled("NAME", Style.EMPTY.bold())),
+ Cell.from(Span.styled("STATUS", Style.EMPTY.bold())),
+ Cell.from(Span.styled("KIND", Style.EMPTY.bold())),
+ Cell.from(Span.styled("MESSAGE", Style.EMPTY.bold()))))
.widths(
Constraint.length(12),
Constraint.length(25),
Constraint.length(12),
Constraint.length(6),
Constraint.fill())
- .highlightStyle(Style.create().fg(Color.WHITE).bold().onBlue())
+ .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
.highlightSpacing(Table.HighlightSpacing.ALWAYS)
.block(Block.builder().borderType(BorderType.ROUNDED).title(title).build())
.build();
@@ -1465,8 +1495,8 @@ public class CamelMonitor extends CamelCommand {
// Memory gauge
if (info.heapMemMax > 0) {
int pct = (int) (100.0 * info.heapMemUsed / info.heapMemMax);
- Style gaugeStyle = pct > 80 ? Style.create().fg(Color.RED)
- : pct > 60 ? Style.create().fg(Color.YELLOW) :
Style.create().fg(Color.GREEN);
+ Style gaugeStyle = pct > 80 ? Style.EMPTY.fg(Color.RED)
+ : pct > 60 ? Style.EMPTY.fg(Color.YELLOW) :
Style.EMPTY.fg(Color.GREEN);
Gauge gauge = Gauge.builder()
.percent(pct)
.label(String.format("Heap: %s / %s (%d%%)",
formatBytes(info.heapMemUsed),
@@ -1499,9 +1529,9 @@ public class CamelMonitor extends CamelCommand {
for (EndpointInfo ep : info.endpoints) {
String dir = ep.direction != null ? ep.direction : "";
Style dirStyle = switch (dir) {
- case "in" -> Style.create().fg(Color.GREEN);
- case "out" -> Style.create().fg(Color.BLUE);
- default -> Style.create().fg(Color.YELLOW);
+ case "in" -> Style.EMPTY.fg(Color.GREEN);
+ case "out" -> Style.EMPTY.fg(Color.BLUE);
+ default -> Style.EMPTY.fg(Color.YELLOW);
};
String arrow = switch (dir) {
case "in" -> "\u2192 ";
@@ -1510,7 +1540,7 @@ public class CamelMonitor extends CamelCommand {
};
rows.add(Row.from(
- Cell.from(Span.styled(ep.component != null ? ep.component
: "", Style.create().fg(Color.CYAN))),
+ Cell.from(Span.styled(ep.component != null ? ep.component
: "", Style.EMPTY.fg(Color.CYAN))),
Cell.from(Span.styled(arrow + dir, dirStyle)),
Cell.from(ep.uri != null ? ep.uri : ""),
Cell.from(ep.routeId != null ? ep.routeId : "")));
@@ -1519,7 +1549,7 @@ public class CamelMonitor extends CamelCommand {
if (rows.isEmpty()) {
rows.add(Row.from(
Cell.from(""),
- Cell.from(Span.styled("No endpoints",
Style.create().dim())),
+ Cell.from(Span.styled("No endpoints", Style.EMPTY.dim())),
Cell.from(""),
Cell.from("")));
}
@@ -1527,16 +1557,16 @@ public class CamelMonitor extends CamelCommand {
Table table = Table.builder()
.rows(rows)
.header(Row.from(
- Cell.from(Span.styled("COMPONENT",
Style.create().bold())),
- Cell.from(Span.styled("DIR", Style.create().bold())),
- Cell.from(Span.styled("URI", Style.create().bold())),
- Cell.from(Span.styled("ROUTE",
Style.create().bold()))))
+ Cell.from(Span.styled("COMPONENT",
Style.EMPTY.bold())),
+ Cell.from(Span.styled("DIR", Style.EMPTY.bold())),
+ Cell.from(Span.styled("URI", Style.EMPTY.bold())),
+ Cell.from(Span.styled("ROUTE", Style.EMPTY.bold()))))
.widths(
Constraint.length(15),
Constraint.length(8),
Constraint.fill(),
Constraint.length(20))
- .highlightStyle(Style.create().fg(Color.WHITE).bold().onBlue())
+ .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
.highlightSpacing(Table.HighlightSpacing.ALWAYS)
.block(Block.builder().borderType(BorderType.ROUNDED)
.title(" Endpoints [" + info.name + "] ").build())
@@ -1570,16 +1600,16 @@ public class CamelMonitor extends CamelCommand {
List<Row> rows = new ArrayList<>();
for (LogEntry entry : filteredLogEntries) {
Style levelStyle = switch (entry.level) {
- case "ERROR", "FATAL" -> Style.create().fg(Color.RED);
- case "WARN" -> Style.create().fg(Color.YELLOW);
- case "DEBUG", "TRACE" -> Style.create().dim();
- default -> Style.create();
+ case "ERROR", "FATAL" -> Style.EMPTY.fg(Color.RED);
+ case "WARN" -> Style.EMPTY.fg(Color.YELLOW);
+ case "DEBUG", "TRACE" -> Style.EMPTY.dim();
+ default -> Style.EMPTY;
};
rows.add(Row.from(
- Cell.from(Span.styled(entry.time, Style.create().dim())),
+ Cell.from(Span.styled(entry.time, Style.EMPTY.dim())),
Cell.from(Span.styled(entry.level, levelStyle)),
- Cell.from(Span.styled(entry.logger != null ? entry.logger
: "", Style.create().fg(Color.CYAN))),
+ Cell.from(Span.styled(entry.logger != null ? entry.logger
: "", Style.EMPTY.fg(Color.CYAN))),
Cell.from(Span.styled(entry.message, levelStyle))));
}
@@ -1587,16 +1617,16 @@ public class CamelMonitor extends CamelCommand {
Table logTable = Table.builder()
.rows(rows)
.header(Row.from(
- Cell.from(Span.styled("TIME", Style.create().bold())),
- Cell.from(Span.styled("LEVEL", Style.create().bold())),
- Cell.from(Span.styled("LOGGER",
Style.create().bold())),
- Cell.from(Span.styled("MESSAGE",
Style.create().bold()))))
+ Cell.from(Span.styled("TIME", Style.EMPTY.bold())),
+ Cell.from(Span.styled("LEVEL", Style.EMPTY.bold())),
+ Cell.from(Span.styled("LOGGER", Style.EMPTY.bold())),
+ Cell.from(Span.styled("MESSAGE", Style.EMPTY.bold()))))
.widths(
Constraint.length(12),
Constraint.length(6),
Constraint.length(20),
Constraint.fill())
- .highlightStyle(Style.create().fg(Color.WHITE).bold().onBlue())
+ .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
.highlightSpacing(Table.HighlightSpacing.ALWAYS)
.block(Block.builder().borderType(BorderType.ROUNDED)
.title(" Log [" + info.name + "] " +
levelTitle).build())
@@ -1614,7 +1644,7 @@ public class CamelMonitor extends CamelCommand {
frame.renderWidget(
Paragraph.builder()
.text(Text.from(Line.from(
- Span.styled(" Select a log entry",
Style.create().dim()))))
+ Span.styled(" Select a log entry",
Style.EMPTY.dim()))))
.block(Block.builder().borderType(BorderType.ROUNDED)
.title(" Detail ").build())
.build(),
@@ -1635,10 +1665,10 @@ public class CamelMonitor extends CamelCommand {
private Style colorStyleForLevel(String level) {
return switch (level) {
- case "ERROR", "FATAL" -> Style.create().fg(Color.RED);
- case "WARN" -> Style.create().fg(Color.YELLOW);
- case "DEBUG", "TRACE" -> Style.create().dim();
- default -> Style.create();
+ case "ERROR", "FATAL" -> Style.EMPTY.fg(Color.RED);
+ case "WARN" -> Style.EMPTY.fg(Color.YELLOW);
+ case "DEBUG", "TRACE" -> Style.EMPTY.dim();
+ default -> Style.EMPTY;
};
}
@@ -1655,8 +1685,8 @@ public class CamelMonitor extends CamelCommand {
return sb.toString();
}
- private void readLogFile(String pid) {
- logLines.clear();
+ private void readLogFile(String pid, List<String> target) {
+ target.clear();
Path logFile = CommandLineHelper.getCamelDir().resolve(pid + ".log");
if (!Files.exists(logFile)) {
return;
@@ -1673,12 +1703,12 @@ public class CamelMonitor extends CamelCommand {
byte[] remaining = new byte[(int) (length - raf.getFilePointer())];
raf.readFully(remaining);
String content = new String(remaining, StandardCharsets.UTF_8);
- String[] lines = content.split("\n", -1);
- int start = Math.max(0, lines.length - MAX_LOG_LINES);
- for (int i = start; i < lines.length; i++) {
- String line = lines[i].replaceAll("\u001B\\[[;\\d]*m", "");
+ String[] rawLines = content.split("\n", -1);
+ int start = Math.max(0, rawLines.length - MAX_LOG_LINES);
+ for (int i = start; i < rawLines.length; i++) {
+ String line = rawLines[i].replaceAll("\u001B\\[[;\\d]*m", "");
if (!line.isEmpty()) {
- logLines.add(line);
+ target.add(line);
}
}
} catch (IOException e) {
@@ -1686,15 +1716,16 @@ public class CamelMonitor extends CamelCommand {
}
}
- private void applyLogFilters() {
- filteredLogEntries.clear();
- for (String line : logLines) {
+ private List<LogEntry> applyLogFilters(List<String> lines) {
+ List<LogEntry> result = new ArrayList<>();
+ for (String line : lines) {
LogEntry entry = parseLogLine(line);
if (!matchesLogLevelFilter(entry.level)) {
continue;
}
- filteredLogEntries.add(entry);
+ result.add(entry);
}
+ return result;
}
// Regex for Spring Boot / Camel log format:
@@ -1775,10 +1806,10 @@ public class CamelMonitor extends CamelCommand {
for (TraceEntry entry : current) {
String status = entry.status != null ? entry.status : "";
Style statusStyle = switch (status) {
- case "Done" -> Style.create().fg(Color.GREEN);
- case "Failed" -> Style.create().fg(Color.RED);
- case "Processing" -> Style.create().fg(Color.YELLOW);
- default -> Style.create().fg(Color.WHITE);
+ case "Done" -> Style.EMPTY.fg(Color.GREEN);
+ case "Failed" -> Style.EMPTY.fg(Color.RED);
+ case "Processing" -> Style.EMPTY.fg(Color.YELLOW);
+ default -> Style.EMPTY.fg(Color.WHITE);
};
String bodyPreview = entry.bodyPreview != null ?
truncate(entry.bodyPreview, 40) : "";
@@ -1788,7 +1819,7 @@ public class CamelMonitor extends CamelCommand {
Cell.from(entry.pid != null ? entry.pid : ""),
Cell.from(Span.styled(
entry.routeId != null ? truncate(entry.routeId,
15) : "",
- Style.create().fg(Color.CYAN))),
+ Style.EMPTY.fg(Color.CYAN))),
Cell.from(entry.nodeId != null ? truncate(entry.nodeId,
15) : ""),
Cell.from(Span.styled(status, statusStyle)),
Cell.from(entry.elapsed + "ms"),
@@ -1796,13 +1827,13 @@ public class CamelMonitor extends CamelCommand {
}
Row header = Row.from(
- Cell.from(Span.styled("TIME", Style.create().bold())),
- Cell.from(Span.styled("PID", Style.create().bold())),
- Cell.from(Span.styled("ROUTE", Style.create().bold())),
- Cell.from(Span.styled("NODE", Style.create().bold())),
- Cell.from(Span.styled("STATUS", Style.create().bold())),
- Cell.from(Span.styled("ELAPSED", Style.create().bold())),
- Cell.from(Span.styled("BODY", Style.create().bold())));
+ Cell.from(Span.styled("TIME", Style.EMPTY.bold())),
+ Cell.from(Span.styled("PID", Style.EMPTY.bold())),
+ Cell.from(Span.styled("ROUTE", Style.EMPTY.bold())),
+ Cell.from(Span.styled("NODE", Style.EMPTY.bold())),
+ Cell.from(Span.styled("STATUS", Style.EMPTY.bold())),
+ Cell.from(Span.styled("ELAPSED", Style.EMPTY.bold())),
+ Cell.from(Span.styled("BODY", Style.EMPTY.bold())));
String traceTitle = String.format(" Traces [%d] %s ",
current.size(),
@@ -1819,7 +1850,7 @@ public class CamelMonitor extends CamelCommand {
Constraint.length(12),
Constraint.length(10),
Constraint.fill())
- .highlightStyle(Style.create().fg(Color.WHITE).bold().onBlue())
+ .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
.highlightSpacing(Table.HighlightSpacing.ALWAYS)
.block(Block.builder().borderType(BorderType.ROUNDED).title(traceTitle).build())
.build();
@@ -1838,7 +1869,7 @@ public class CamelMonitor extends CamelCommand {
Paragraph.builder()
.text(Text.from(Line.from(
Span.styled(" Select a trace entry to view
details",
- Style.create().dim()))))
+ Style.EMPTY.dim()))))
.block(Block.builder().borderType(BorderType.ROUNDED)
.title(" Detail ").build())
.build(),
@@ -1851,22 +1882,22 @@ public class CamelMonitor extends CamelCommand {
// Exchange info
lines.add(Line.from(
- Span.styled(" Exchange: ",
Style.create().fg(Color.YELLOW).bold()),
+ Span.styled(" Exchange: ",
Style.EMPTY.fg(Color.YELLOW).bold()),
Span.raw(entry.exchangeId != null ? entry.exchangeId : "")));
lines.add(Line.from(
- Span.styled(" UID: ",
Style.create().fg(Color.YELLOW).bold()),
+ Span.styled(" UID: ",
Style.EMPTY.fg(Color.YELLOW).bold()),
Span.raw(entry.uid != null ? entry.uid : "")));
lines.add(Line.from(
- Span.styled(" Location: ",
Style.create().fg(Color.YELLOW).bold()),
+ Span.styled(" Location: ",
Style.EMPTY.fg(Color.YELLOW).bold()),
Span.raw(entry.location != null ? entry.location : "")));
lines.add(Line.from(
- Span.styled(" Route: ",
Style.create().fg(Color.YELLOW).bold()),
+ Span.styled(" Route: ",
Style.EMPTY.fg(Color.YELLOW).bold()),
Span.raw(entry.routeId != null ? entry.routeId : ""),
Span.raw(" Node: "),
Span.raw(entry.nodeId != null ? entry.nodeId : ""),
Span.raw(entry.nodeLabel != null ? " (" + entry.nodeLabel +
")" : "")));
lines.add(Line.from(
- Span.styled(" Status: ",
Style.create().fg(Color.YELLOW).bold()),
+ Span.styled(" Status: ",
Style.EMPTY.fg(Color.YELLOW).bold()),
Span.raw(entry.status != null ? entry.status : ""),
Span.raw(" Elapsed: "),
Span.raw(entry.elapsed + "ms")));
@@ -1874,10 +1905,10 @@ public class CamelMonitor extends CamelCommand {
// Headers
if (showTraceHeaders && entry.headers != null &&
!entry.headers.isEmpty()) {
- lines.add(Line.from(Span.styled(" Headers:",
Style.create().fg(Color.GREEN).bold())));
+ lines.add(Line.from(Span.styled(" Headers:",
Style.EMPTY.fg(Color.GREEN).bold())));
for (Map.Entry<String, Object> h : entry.headers.entrySet()) {
lines.add(Line.from(
- Span.styled(" " + h.getKey(),
Style.create().fg(Color.CYAN)),
+ Span.styled(" " + h.getKey(),
Style.EMPTY.fg(Color.CYAN)),
Span.raw(" = "),
Span.raw(h.getValue() != null ?
h.getValue().toString() : "null")));
}
@@ -1886,7 +1917,7 @@ public class CamelMonitor extends CamelCommand {
// Body
if (showTraceBody && entry.body != null) {
- lines.add(Line.from(Span.styled(" Body:",
Style.create().fg(Color.GREEN).bold())));
+ lines.add(Line.from(Span.styled(" Body:",
Style.EMPTY.fg(Color.GREEN).bold())));
String[] bodyLines = entry.body.split("\n");
for (String bl : bodyLines) {
lines.add(Line.from(Span.raw(" " + bl)));
@@ -1896,10 +1927,10 @@ public class CamelMonitor extends CamelCommand {
// Exchange properties
if (entry.exchangeProperties != null &&
!entry.exchangeProperties.isEmpty()) {
- lines.add(Line.from(Span.styled(" Exchange Properties:",
Style.create().fg(Color.GREEN).bold())));
+ lines.add(Line.from(Span.styled(" Exchange Properties:",
Style.EMPTY.fg(Color.GREEN).bold())));
for (Map.Entry<String, Object> p :
entry.exchangeProperties.entrySet()) {
lines.add(Line.from(
- Span.styled(" " + p.getKey(),
Style.create().fg(Color.CYAN)),
+ Span.styled(" " + p.getKey(),
Style.EMPTY.fg(Color.CYAN)),
Span.raw(" = "),
Span.raw(p.getValue() != null ?
p.getValue().toString() : "null")));
}
@@ -1908,10 +1939,10 @@ public class CamelMonitor extends CamelCommand {
// Exchange variables
if (entry.exchangeVariables != null &&
!entry.exchangeVariables.isEmpty()) {
- lines.add(Line.from(Span.styled(" Exchange Variables:",
Style.create().fg(Color.GREEN).bold())));
+ lines.add(Line.from(Span.styled(" Exchange Variables:",
Style.EMPTY.fg(Color.GREEN).bold())));
for (Map.Entry<String, Object> v :
entry.exchangeVariables.entrySet()) {
lines.add(Line.from(
- Span.styled(" " + v.getKey(),
Style.create().fg(Color.CYAN)),
+ Span.styled(" " + v.getKey(),
Style.EMPTY.fg(Color.CYAN)),
Span.raw(" = "),
Span.raw(v.getValue() != null ?
v.getValue().toString() : "null")));
}
@@ -1935,7 +1966,7 @@ public class CamelMonitor extends CamelCommand {
Paragraph.builder()
.text(Text.from(Line.from(
Span.styled(" Select an integration from the
Overview tab (press 1)",
- Style.create().dim()))))
+ Style.EMPTY.dim()))))
.block(Block.builder().borderType(BorderType.ROUNDED)
.title(" No integration selected ").build())
.build(),
@@ -1943,108 +1974,102 @@ public class CamelMonitor extends CamelCommand {
}
private void renderFooter(Frame frame, Rect area) {
- String refreshLabel = refreshInterval >= 1000
- ? (refreshInterval / 1000) + "s"
- : refreshInterval + "ms";
- Line footer;
+ List<Span> spans = new ArrayList<>();
int tab = tabsState.selected();
+
if (tab == TAB_OVERVIEW) {
- footer = Line.from(
- Span.styled(" q", Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" quit "),
- Span.styled("r", Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" refresh "),
- Span.styled("\u2191\u2193",
Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" navigate "),
- Span.styled("Enter",
Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" details "),
- Span.styled("1-6", Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" tabs "),
- Span.styled("Refresh: " + refreshLabel,
Style.create().dim()));
+ hint(spans, "q", "quit");
+ hint(spans, "r", "refresh");
+ hint(spans, "\u2191\u2193", "navigate");
+ hint(spans, "Enter", "details");
+ hint(spans, "1-6", "tabs");
+ hintRefresh(spans);
+ } else if (tab == TAB_ROUTES && showDiagram) {
+ String closeKey = diagramTextMode ? "D" : "d";
+ hint(spans, closeKey + "/Esc", "close");
+ hint(spans, "\u2191\u2193\u2190\u2192", "scroll");
+ hint(spans, "PgUp/PgDn", "page");
+ hintLast(spans, "Home/End", "top/bottom");
} else if (tab == TAB_ROUTES) {
- if (showDiagram) {
- String closeKey = diagramTextMode ? "D" : "d";
- footer = Line.from(
- 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\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.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()),
- Span.raw(" back "),
- Span.styled("\u2191\u2193",
Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" navigate "),
- Span.styled("s",
Style.create().fg(Color.YELLOW).bold()),
- 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()));
- }
+ hint(spans, "Esc", "back");
+ hint(spans, "\u2191\u2193", "navigate");
+ hint(spans, "s", "sort");
+ hint(spans, "d", "diagram");
+ hint(spans, "D", "text diagram");
+ hint(spans, "1-6", "tabs");
+ hintRefresh(spans);
} else if (tab == TAB_HEALTH) {
- footer = Line.from(
- Span.styled(" Esc",
Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" back "),
- Span.styled("\u2191\u2193",
Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" navigate "),
- Span.styled("d", Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" toggle DOWN "),
- Span.styled("1-6", Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" tabs "),
- Span.styled("Refresh: " + refreshLabel,
Style.create().dim()));
+ hint(spans, "Esc", "back");
+ hint(spans, "\u2191\u2193", "navigate");
+ hint(spans, "d", "toggle DOWN");
+ hint(spans, "1-6", "tabs");
+ hintRefresh(spans);
} else if (tab == TAB_LOG) {
- footer = Line.from(
- Span.styled(" Esc",
Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" back "),
- Span.styled("\u2191\u2193",
Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" scroll "),
- Span.styled("PgUp/PgDn",
Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" page "),
- Span.styled("t/d/i/w/e",
Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" levels "),
- Span.styled("f", Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" follow "),
- Span.styled("g/G", Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" top/end"));
+ hint(spans, "Esc", "back");
+ hint(spans, "\u2191\u2193", "scroll");
+ hint(spans, "PgUp/PgDn", "page");
+ hint(spans, "Home/End", "top/end");
+ hint(spans, "t/d/i/w/e", "levels");
+ hintLast(spans, "f", "follow");
} else if (tab == TAB_TRACE) {
- footer = Line.from(
- Span.styled(" Esc",
Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" back "),
- Span.styled("\u2191\u2193",
Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" navigate "),
- Span.styled("h", Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" headers" + (showTraceHeaders ? " [on]" : "
[off]") + " "),
- Span.styled("b", Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" body" + (showTraceBody ? " [on]" : " [off]") +
" "),
- Span.styled("f", Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" follow" + (traceFollowMode ? " [on]" : "
[off]")));
+ hint(spans, "Esc", "back");
+ hint(spans, "\u2191\u2193", "navigate");
+ hint(spans, "h", "headers" + (showTraceHeaders ? " [on]" : "
[off]"));
+ hint(spans, "b", "body" + (showTraceBody ? " [on]" : " [off]"));
+ hintLast(spans, "f", "follow" + (traceFollowMode ? " [on]" : "
[off]"));
} else {
- footer = Line.from(
- Span.styled(" Esc",
Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" back "),
- Span.styled("\u2191\u2193",
Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" navigate "),
- Span.styled("1-6", Style.create().fg(Color.YELLOW).bold()),
- Span.raw(" tabs "),
- Span.styled("Refresh: " + refreshLabel,
Style.create().dim()));
+ hint(spans, "Esc", "back");
+ hint(spans, "\u2191\u2193", "navigate");
+ hint(spans, "1-6", "tabs");
+ hintRefresh(spans);
}
- frame.renderWidget(Paragraph.from(footer), area);
+ frame.renderWidget(Paragraph.from(Line.from(spans)), area);
+ }
+
+ private static final Style HINT_KEY_STYLE =
Style.EMPTY.fg(Color.YELLOW).bold();
+
+ private static void hint(List<Span> spans, String key, String label) {
+ spans.add(Span.styled(" " + key, HINT_KEY_STYLE));
+ spans.add(Span.raw(" " + label + " "));
+ }
+
+ private static void hintLast(List<Span> spans, String key, String label) {
+ spans.add(Span.styled(" " + key, HINT_KEY_STYLE));
+ spans.add(Span.raw(" " + label));
+ }
+
+ private void hintRefresh(List<Span> spans) {
+ String refreshLabel = refreshInterval >= 1000
+ ? (refreshInterval / 1000) + "s"
+ : refreshInterval + "ms";
+ spans.add(Span.styled("Refresh: " + refreshLabel, Style.EMPTY.dim()));
}
// ---- Data Loading ----
private void refreshData() {
+ if (runner == null) {
+ refreshDataSync();
+ return;
+ }
+ if (!refreshInProgress.compareAndSet(false, true)) {
+ return;
+ }
+ lastRefresh = System.currentTimeMillis();
+ String currentSelectedPid = selectedPid;
+ runner.scheduler().execute(() -> {
+ try {
+ refreshDataSync();
+ } finally {
+ refreshInProgress.set(false);
+ }
+ runner.runOnRenderThread(() -> {
+ });
+ });
+ }
+
+ private void refreshDataSync() {
lastRefresh = System.currentTimeMillis();
try {
List<IntegrationInfo> infos = new ArrayList<>();
@@ -2057,7 +2082,6 @@ public class CamelMonitor extends CamelCommand {
IntegrationInfo info = parseIntegration(ph, root);
if (info != null) {
infos.add(info);
- // Track throughput for sparkline
updateThroughputHistory(info);
}
}
@@ -2095,8 +2119,11 @@ public class CamelMonitor extends CamelCommand {
// Refresh log data for the selected integration
IntegrationInfo selected = findSelectedIntegration();
if (selected != null) {
- readLogFile(selected.pid);
- applyLogFilters();
+ List<String> newLogLines = new ArrayList<>();
+ readLogFile(selected.pid, newLogLines);
+ List<LogEntry> newFilteredEntries =
applyLogFilters(newLogLines);
+ logLines = newLogLines;
+ filteredLogEntries = newFilteredEntries;
}
// Refresh trace data
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 87b7cc74d9e3..0183a53b139e 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
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
+import dev.tamboui.text.CharWidth;
import org.apache.camel.dsl.jbang.core.common.ProcessHelper;
import org.apache.camel.support.PatternHelper;
import org.apache.camel.util.FileUtil;
@@ -109,7 +110,9 @@ final class TuiHelper {
if (s == null) {
return "";
}
- return s.length() > max ? s.substring(0, max - 1) + "\u2026" : s;
+ return CharWidth.of(s) > max
+ ? CharWidth.truncateWithEllipsis(s, max,
CharWidth.TruncatePosition.END)
+ : s;
}
/**