You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/05/31 07:50:39 UTC
(camel) branch CAMEL-23648-tui-send-fullscreen created (now 7a2126fa8e08)
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a change to branch CAMEL-23648-tui-send-fullscreen
in repository https://gitbox.apache.org/repos/asf/camel.git
at 7a2126fa8e08 CAMEL-23648: camel-jbang - TUI Memory tab add GC key and dual heap gauge bars
This branch includes the following new commits:
new ffb2d12ceaf8 CAMEL-23648: camel-jbang - TUI redesign F2 Send Message as full-screen view
new a00e52b2b541 CAMEL-23648: camel-jbang - TUI remove 1-9 tabs footer hints and fix Inspect badge count
new db250d885475 CAMEL-23648: camel-jbang - TUI add Reset Screen action to F2 menu
new ae7dd51df3b7 CAMEL-23648: camel-jbang - Sync bundled examples from camel-jbang-examples repo
new ccbbc340f315 CAMEL-23648: camel-jbang - TUI fix Reset Screen menu item rendering
new 7a2126fa8e08 CAMEL-23648: camel-jbang - TUI Memory tab add GC key and dual heap gauge bars
The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(camel) 06/06: CAMEL-23648: camel-jbang - TUI Memory tab add GC key and dual heap gauge bars
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch CAMEL-23648-tui-send-fullscreen
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 7a2126fa8e0885ccc70f3cbf247b282096b9a8d9
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sun May 31 09:50:14 2026 +0200
CAMEL-23648: camel-jbang - TUI Memory tab add GC key and dual heap gauge bars
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../dsl/jbang/core/commands/tui/MemoryTab.java | 57 ++++++++++++++++------
1 file changed, 43 insertions(+), 14 deletions(-)
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
index 715c0e667ed3..6ed257569b79 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.dsl.jbang.core.commands.tui;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@@ -35,7 +36,9 @@ import dev.tamboui.tui.event.KeyEvent;
import dev.tamboui.widgets.block.Block;
import dev.tamboui.widgets.block.BorderType;
import dev.tamboui.widgets.paragraph.Paragraph;
+import org.apache.camel.dsl.jbang.core.common.PathUtils;
import org.apache.camel.util.TimeUtils;
+import org.apache.camel.util.json.JsonObject;
import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*;
@@ -55,6 +58,10 @@ class MemoryTab implements MonitorTab {
@Override
public boolean handleKeyEvent(KeyEvent ke) {
+ if (ke.isChar('g')) {
+ triggerGC();
+ return true;
+ }
return false;
}
@@ -80,7 +87,7 @@ class MemoryTab implements MonitorTab {
}
// Layout: stats panel + chart row (14 + 1 for axis)
- int statsHeight = 10;
+ int statsHeight = 11;
if (info.oldGenUsed > 0) {
statsHeight += 2;
}
@@ -112,24 +119,34 @@ class MemoryTab implements MonitorTab {
private void renderStats(Frame frame, Rect area, IntegrationInfo info) {
List<Line> lines = new ArrayList<>();
- // Heap memory with gauge bar
+ // Heap memory with two gauge bars (used/committed and used/max)
if (info.heapMemUsed > 0) {
- long pct = info.heapMemMax > 0 ? info.heapMemUsed * 100 / info.heapMemMax : 0;
- String gauge = buildGaugeBar(pct, 30);
- Color gaugeColor = pct >= 80 ? Color.LIGHT_RED : pct >= 60 ? Color.YELLOW : Color.GREEN;
-
lines.add(Line.from(
Span.styled(" Heap Memory", Style.EMPTY.fg(Color.CYAN).bold())));
lines.add(Line.from(
Span.styled(" used: ", Style.EMPTY.dim()),
- Span.styled(String.format("%-10s", formatBytes(info.heapMemUsed)), Style.EMPTY.fg(Color.WHITE).bold()),
- Span.styled(gauge, Style.EMPTY.fg(gaugeColor)),
- Span.styled(String.format(" %d%%", pct), Style.EMPTY.fg(gaugeColor).bold())));
- lines.add(Line.from(
- Span.styled(" committed: ", Style.EMPTY.dim()),
- Span.raw(formatBytes(info.heapMemCommitted)),
- Span.styled(" max: ", Style.EMPTY.dim()),
- Span.raw(formatBytes(info.heapMemMax))));
+ Span.styled(formatBytes(info.heapMemUsed), Style.EMPTY.fg(Color.WHITE).bold())));
+
+ if (info.heapMemCommitted > 0) {
+ long pctComm = info.heapMemUsed * 100 / info.heapMemCommitted;
+ String gaugeComm = buildGaugeBar(pctComm, 30);
+ Color colorComm = pctComm >= 80 ? Color.LIGHT_RED : pctComm >= 60 ? Color.YELLOW : Color.GREEN;
+ lines.add(Line.from(
+ Span.styled(" committed: ", Style.EMPTY.dim()),
+ Span.styled(String.format("%-10s", formatBytes(info.heapMemCommitted)), Style.EMPTY),
+ Span.styled(gaugeComm, Style.EMPTY.fg(colorComm)),
+ Span.styled(String.format(" %d%%", pctComm), Style.EMPTY.fg(colorComm).bold())));
+ }
+ if (info.heapMemMax > 0) {
+ long pctMax = info.heapMemUsed * 100 / info.heapMemMax;
+ String gaugeMax = buildGaugeBar(pctMax, 30);
+ Color colorMax = pctMax >= 80 ? Color.LIGHT_RED : pctMax >= 60 ? Color.YELLOW : Color.GREEN;
+ lines.add(Line.from(
+ Span.styled(" max: ", Style.EMPTY.dim()),
+ Span.styled(String.format("%-10s", formatBytes(info.heapMemMax)), Style.EMPTY),
+ Span.styled(gaugeMax, Style.EMPTY.fg(colorMax)),
+ Span.styled(String.format(" %d%%", pctMax), Style.EMPTY.fg(colorMax).bold())));
+ }
}
// Old Gen pool
@@ -309,6 +326,18 @@ class MemoryTab implements MonitorTab {
@Override
public void renderFooter(List<Span> spans) {
hint(spans, "Esc", "back");
+ hint(spans, "g", "gc");
+ }
+
+ private void triggerGC() {
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (info == null) {
+ return;
+ }
+ JsonObject root = new JsonObject();
+ root.put("action", "gc");
+ Path actionFile = ctx.getActionFile(info.pid);
+ PathUtils.writeTextSafely(root.toJson(), actionFile);
}
private static String buildGaugeBar(long pct, int width) {
(camel) 02/06: CAMEL-23648: camel-jbang - TUI remove 1-9 tabs footer hints and fix Inspect badge count
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch CAMEL-23648-tui-send-fullscreen
in repository https://gitbox.apache.org/repos/asf/camel.git
commit a00e52b2b5412144917ae5bd2e70f62eb65ea7a9
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sun May 31 08:43:13 2026 +0200
CAMEL-23648: camel-jbang - TUI remove 1-9 tabs footer hints and fix Inspect badge count
Remove redundant 1-9/1-0/1-2 tab switching hints from footer across all
tabs since the tab bar at the top already shows available tabs. Fix the
Inspect tab badge to show count of distinct exchanges instead of individual
processing steps.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java | 4 +++-
.../apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java | 1 -
.../org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java | 1 -
.../org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java | 1 -
.../java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java | 1 -
.../java/org/apache/camel/dsl/jbang/core/commands/tui/HealthTab.java | 1 -
.../java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java | 4 ----
.../org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java | 1 -
.../java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java | 1 -
.../java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java | 1 -
.../java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java | 1 -
.../org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java | 1 -
.../java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java | 1 -
13 files changed, 3 insertions(+), 16 deletions(-)
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
index 0c214269aab0..33dbb3c5faf6 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
@@ -1016,7 +1016,9 @@ public class CamelMonitor extends CamelCommand {
int healthCount = hasSelection ? sel.healthChecks.size() : 0;
long healthDownCount = hasSelection
? sel.healthChecks.stream().filter(hc -> "DOWN".equals(hc.state)).count() : 0;
- int historyCount = hasSelection ? historyTab.historyEntries.size() : 0;
+ long historyCount = hasSelection
+ ? historyTab.historyEntries.stream().map(e -> e.exchangeId).distinct().count()
+ : 0;
boolean hasTraces = hasSelection && !traces.get().isEmpty();
int httpCount = hasSelection ? sel.httpEndpoints.size() : 0;
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
index 43ef5244838f..30bb0ca5363e 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
@@ -207,7 +207,6 @@ class CircuitBreakerTab implements MonitorTab {
hint(spans, "Esc", "back");
hint(spans, "↑↓", "navigate");
hint(spans, "s", "sort");
- hint(spans, "1-9", "tabs");
}
private String sortLabel(String label, String column) {
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 2dc2071ab825..d5521ea59928 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
@@ -157,7 +157,6 @@ class ConsumersTab implements MonitorTab {
public void renderFooter(List<Span> spans) {
hint(spans, "Esc", "back");
hint(spans, "s", "sort");
- hint(spans, "1-9", "tabs");
}
private String sortLabel(String label, String column) {
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
index 5a7ca27709b8..c9225ef56d03 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
@@ -297,7 +297,6 @@ class EndpointsTab implements MonitorTab {
default -> "[off]";
};
hint(spans, "a", "chart " + chartLabel);
- hint(spans, "1-9", "tabs");
}
int getFilter() {
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 edec0c7c4982..f68536af2edd 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
@@ -273,7 +273,6 @@ class ErrorsTab implements MonitorTab {
hint(spans, "h", "headers [" + (showHeaders ? "on" : "off") + "]");
hint(spans, "b", "body [" + (showBody ? "on" : "off") + "]");
hint(spans, "w", "wrap [" + (wordWrap ? "on" : "off") + "]");
- hint(spans, "1-0", "tabs");
}
}
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HealthTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HealthTab.java
index f4db0025ab3c..ac3f3ff9866d 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HealthTab.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HealthTab.java
@@ -163,7 +163,6 @@ class HealthTab implements MonitorTab {
hint(spans, "Esc", "back");
hint(spans, "s", "sort");
hint(spans, "d", "toggle DOWN");
- hint(spans, "1-9", "tabs");
}
boolean isShowOnlyDown() {
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
index 7f7938addd1b..e102c285e6ee 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
@@ -263,8 +263,6 @@ class HttpTab implements MonitorTab {
hint(spans, "p", "pretty" + (probePrettyPrint ? " [on]" : ""));
if (!probeHistory.isEmpty()) {
hintLast(spans, "↑↓", "history");
- } else {
- hintLast(spans, "1-9", "tabs");
}
return;
}
@@ -284,8 +282,6 @@ class HttpTab implements MonitorTab {
Integer hSel = tableState.selected();
if (hSel != null && hSel >= 0 && hSel < hVisible.size() && hVisible.get(hSel).specificationUri != null) {
hintLast(spans, "c", "spec");
- } else {
- hintLast(spans, "1-9", "tabs");
}
}
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 7c1314335996..dfeb76fbb711 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
@@ -167,7 +167,6 @@ class InflightTab implements MonitorTab {
public void renderFooter(List<Span> spans) {
hint(spans, "Esc", "back");
hint(spans, "s", "sort");
- hint(spans, "1-9", "tabs");
}
private void renderBrowseDisabled(Frame frame, Rect area) {
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
index a66d0661a549..efaa01e49b17 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
@@ -307,7 +307,6 @@ class LogTab implements MonitorTab {
hint(spans, "l", "level");
}
hint(spans, "f", "follow" + (followMode ? " [on]" : " [off]"));
- hint(spans, ctx.isInfraSelected() ? "1-2" : "1-9", "tabs");
}
private void renderLogLevelPopup(Frame frame, Rect area) {
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
index 7ed318c6eada..715c0e667ed3 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
@@ -309,7 +309,6 @@ class MemoryTab implements MonitorTab {
@Override
public void renderFooter(List<Span> spans) {
hint(spans, "Esc", "back");
- hint(spans, "1-9", "tabs");
}
private static String buildGaugeBar(long pct, int width) {
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 4a4df616b98f..19bf722a7d0b 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
@@ -614,7 +614,6 @@ class MetricsTab implements MonitorTab {
if (info != null && findMetricsUrl(info) != null) {
hint(spans, "r", "raw");
}
- hint(spans, "1-9", "tabs");
}
// ---- Prometheus syntax coloring ----
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 3c088933b2ad..32a5536411b6 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
@@ -593,7 +593,6 @@ class OverviewTab implements MonitorTab {
default -> "[off]";
});
}
- hint(spans, "1-9", "tabs");
}
@Override
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 f8ce5dd3cb6f..ea489639ce94 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
@@ -507,7 +507,6 @@ class RoutesTab implements MonitorTab {
hint(spans, "p", "start");
}
}
- hint(spans, "1-9", "tabs");
}
}
(camel) 01/06: CAMEL-23648: camel-jbang - TUI redesign F2 Send Message as full-screen view
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch CAMEL-23648-tui-send-fullscreen
in repository https://gitbox.apache.org/repos/asf/camel.git
commit ffb2d12ceaf87a28cb7f031c4e2f908a3f0b083e
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sun May 31 08:38:30 2026 +0200
CAMEL-23648: camel-jbang - TUI redesign F2 Send Message as full-screen view
Redesign SendMessagePopup from a small centered popup to a full-screen
3-panel layout (request/response/history) matching the HTTP Probe pattern.
InOut responses now show exchange headers and scrollable body with pretty
print toggle. Adds send history with replay support.
Extract shared form utilities (HeaderEntry, handleTextInput, renderLabel,
handlePaste) into FormHelper to eliminate duplication between SendMessagePopup
and HttpTab. Add Clear + padding to both views for visual separation.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../dsl/jbang/core/commands/tui/ActionsPopup.java | 4 +
.../dsl/jbang/core/commands/tui/FormHelper.java | 72 +++
.../camel/dsl/jbang/core/commands/tui/HttpTab.java | 127 ++--
.../jbang/core/commands/tui/SendMessagePopup.java | 638 ++++++++++++++++-----
4 files changed, 627 insertions(+), 214 deletions(-)
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
index 82fdea42df6e..cc8f3b4dfaf0 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
@@ -589,6 +589,10 @@ class ActionsPopup {
}
void renderFooter(List<Span> spans) {
+ if (sendMessagePopup.isVisible()) {
+ sendMessagePopup.renderFooter(spans);
+ return;
+ }
if (captionOverlay.isInlineMode()) {
captionOverlay.renderFooter(spans);
return;
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FormHelper.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FormHelper.java
new file mode 100644
index 000000000000..98db646f408f
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FormHelper.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.tui;
+
+import dev.tamboui.layout.Rect;
+import dev.tamboui.style.Style;
+import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.Line;
+import dev.tamboui.text.Span;
+import dev.tamboui.tui.event.KeyCode;
+import dev.tamboui.tui.event.KeyEvent;
+import dev.tamboui.widgets.input.TextInputState;
+import dev.tamboui.widgets.paragraph.Paragraph;
+
+final class FormHelper {
+
+ private FormHelper() {
+ }
+
+ record HeaderEntry(TextInputState keyInput, TextInputState valueInput) {
+ }
+
+ static void handleTextInput(KeyEvent ke, TextInputState state) {
+ if (ke.isDeleteBackward()) {
+ state.deleteBackward();
+ } else if (ke.isDeleteForward()) {
+ state.deleteForward();
+ } else if (ke.isLeft()) {
+ state.moveCursorLeft();
+ } else if (ke.isRight()) {
+ state.moveCursorRight();
+ } else if (ke.isHome()) {
+ state.moveCursorToStart();
+ } else if (ke.isEnd()) {
+ state.moveCursorToEnd();
+ } else if (ke.code() == KeyCode.CHAR) {
+ state.insert(ke.character());
+ }
+ }
+
+ static void renderLabel(Frame frame, int x, int y, int w, String label, boolean selected) {
+ Style style = selected ? Style.EMPTY.bold() : Style.EMPTY.dim();
+ Rect labelArea = new Rect(x, y, w, 1);
+ frame.renderWidget(Paragraph.from(Line.from(Span.styled(label, style))), labelArea);
+ }
+
+ static void handlePaste(String text, TextInputState target) {
+ if (text == null || text.isEmpty() || target == null) {
+ return;
+ }
+ for (int i = 0; i < text.length(); i++) {
+ char ch = text.charAt(i);
+ if (ch != '\n' && ch != '\r') {
+ target.insert(ch);
+ }
+ }
+ }
+}
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
index 23c769132f08..7f7938addd1b 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
@@ -43,6 +43,7 @@ import dev.tamboui.text.Span;
import dev.tamboui.text.Text;
import dev.tamboui.tui.event.KeyCode;
import dev.tamboui.tui.event.KeyEvent;
+import dev.tamboui.widgets.Clear;
import dev.tamboui.widgets.block.Block;
import dev.tamboui.widgets.block.BorderType;
import dev.tamboui.widgets.block.Title;
@@ -99,7 +100,7 @@ class HttpTab implements MonitorTab {
private int probeMethodIndex;
private final TextInputState probePathState = new TextInputState("");
private final TextInputState probeBodyState = new TextInputState("");
- private List<HeaderEntry> probeHeaders;
+ private List<FormHelper.HeaderEntry> probeHeaders;
private int probeSelectedHeader;
private boolean probeEditingHeaderKey;
private final AtomicBoolean probeSending = new AtomicBoolean(false);
@@ -289,18 +290,10 @@ class HttpTab implements MonitorTab {
}
void handlePaste(String text) {
- if (!probeMode || probeSending.get() || text == null || text.isEmpty()) {
+ if (!probeMode || probeSending.get()) {
return;
}
- TextInputState target = probeActiveTextInput();
- if (target != null) {
- for (int i = 0; i < text.length(); i++) {
- char ch = text.charAt(i);
- if (ch != '\n' && ch != '\r') {
- target.insert(ch);
- }
- }
- }
+ FormHelper.handlePaste(text, probeActiveTextInput());
}
// ---- Probe mode ----
@@ -374,7 +367,7 @@ class HttpTab implements MonitorTab {
for (int i = 0; i < value.length(); i++) {
valState.insert(value.charAt(i));
}
- probeHeaders.add(new HeaderEntry(keyState, valState));
+ probeHeaders.add(new FormHelper.HeaderEntry(keyState, valState));
}
private boolean handleProbeKeyEvent(KeyEvent ke) {
@@ -453,7 +446,7 @@ class HttpTab implements MonitorTab {
addProbeHeaderEmpty();
return true;
}
- handleTextInput(ke, probePathState);
+ FormHelper.handleTextInput(ke, probePathState);
return true;
}
if (probeField == PROBE_HEADERS) {
@@ -482,7 +475,7 @@ class HttpTab implements MonitorTab {
addProbeHeaderEmpty();
return true;
}
- handleTextInput(ke, probeBodyState);
+ FormHelper.handleTextInput(ke, probeBodyState);
return true;
}
if (probeField == PROBE_HISTORY) {
@@ -510,8 +503,8 @@ class HttpTab implements MonitorTab {
}
private boolean handleProbeHeaderKeyEvent(KeyEvent ke) {
- HeaderEntry current = probeHeaders.get(probeSelectedHeader);
- TextInputState activeInput = probeEditingHeaderKey ? current.keyInput : current.valueInput;
+ FormHelper.HeaderEntry current = probeHeaders.get(probeSelectedHeader);
+ TextInputState activeInput = probeEditingHeaderKey ? current.keyInput() : current.valueInput();
if (ke.isChar('+')) {
addProbeHeaderEmpty();
@@ -542,7 +535,7 @@ class HttpTab implements MonitorTab {
return true;
}
if (ke.isDeleteBackward()) {
- if (probeEditingHeaderKey && current.keyInput.text().isEmpty()) {
+ if (probeEditingHeaderKey && current.keyInput().text().isEmpty()) {
probeHeaders.remove(probeSelectedHeader);
if (probeHeaders.isEmpty()) {
probeHeaders = null;
@@ -594,7 +587,7 @@ class HttpTab implements MonitorTab {
if (probeHeaders == null) {
probeHeaders = new ArrayList<>();
}
- probeHeaders.add(new HeaderEntry(new TextInputState(""), new TextInputState("")));
+ probeHeaders.add(new FormHelper.HeaderEntry(new TextInputState(""), new TextInputState("")));
probeField = PROBE_HEADERS;
probeSelectedHeader = probeHeaders.size() - 1;
probeEditingHeaderKey = true;
@@ -612,8 +605,8 @@ class HttpTab implements MonitorTab {
return probeBodyState;
}
if (probeField == PROBE_HEADERS && hasProbeHeaders()) {
- HeaderEntry he = probeHeaders.get(probeSelectedHeader);
- return probeEditingHeaderKey ? he.keyInput : he.valueInput;
+ FormHelper.HeaderEntry he = probeHeaders.get(probeSelectedHeader);
+ return probeEditingHeaderKey ? he.keyInput() : he.valueInput();
}
return null;
}
@@ -638,8 +631,8 @@ class HttpTab implements MonitorTab {
}
probeHeaders = null;
if (entry.headers != null) {
- for (HeaderEntry he : entry.headers) {
- addProbeHeader(he.keyInput.text(), he.valueInput.text());
+ for (FormHelper.HeaderEntry he : entry.headers) {
+ addProbeHeader(he.keyInput().text(), he.valueInput().text());
}
}
probeField = PROBE_BODY;
@@ -677,16 +670,16 @@ class HttpTab implements MonitorTab {
String baseUrl = probeBaseUrl;
// Snapshot headers
- List<HeaderEntry> headerSnapshot = null;
+ List<FormHelper.HeaderEntry> headerSnapshot = null;
if (hasProbeHeaders()) {
headerSnapshot = new ArrayList<>();
- for (HeaderEntry he : probeHeaders) {
- headerSnapshot.add(new HeaderEntry(
- new TextInputState(he.keyInput.text()),
- new TextInputState(he.valueInput.text())));
+ for (FormHelper.HeaderEntry he : probeHeaders) {
+ headerSnapshot.add(new FormHelper.HeaderEntry(
+ new TextInputState(he.keyInput().text()),
+ new TextInputState(he.valueInput().text())));
}
}
- List<HeaderEntry> hdrs = headerSnapshot;
+ List<FormHelper.HeaderEntry> hdrs = headerSnapshot;
ctx.runner.scheduler().execute(() -> {
try {
@@ -698,7 +691,7 @@ class HttpTab implements MonitorTab {
}
private void doProbeRequestInBackground(
- String baseUrl, String method, String path, String body, List<HeaderEntry> hdrs) {
+ String baseUrl, String method, String path, String body, List<FormHelper.HeaderEntry> hdrs) {
String url = baseUrl + path;
String statusText;
@@ -725,9 +718,9 @@ class HttpTab implements MonitorTab {
// Add user headers
if (hdrs != null) {
- for (HeaderEntry he : hdrs) {
- String k = he.keyInput.text().trim();
- String v = he.valueInput.text();
+ for (FormHelper.HeaderEntry he : hdrs) {
+ String k = he.keyInput().text().trim();
+ String v = he.valueInput().text();
if (!k.isEmpty()) {
reqBuilder.header(k, v);
}
@@ -770,7 +763,7 @@ class HttpTab implements MonitorTab {
}
// Build history entry
- List<HeaderEntry> histHeaders = null;
+ List<FormHelper.HeaderEntry> histHeaders = null;
if (hdrs != null && !hdrs.isEmpty()) {
histHeaders = new ArrayList<>(hdrs);
}
@@ -860,6 +853,14 @@ class HttpTab implements MonitorTab {
// ---- Probe rendering ----
private void renderProbe(Frame frame, Rect area) {
+ frame.renderWidget(Clear.INSTANCE, area);
+
+ int padX = 2;
+ int padY = 1;
+ Rect inner = new Rect(
+ area.left() + padX, area.top() + padY,
+ area.width() - padX * 2, area.height() - padY * 2);
+
int headerCount = hasProbeHeaders() ? probeHeaders.size() : 0;
int requestHeight = 7 + headerCount + (headerCount > 0 ? 1 : 0);
int historyHeight = Math.min(4 + 2, probeHistory.size() + 2);
@@ -872,7 +873,7 @@ class HttpTab implements MonitorTab {
Constraint.length(requestHeight),
Constraint.fill(),
Constraint.length(historyHeight))
- .split(area);
+ .split(inner);
renderProbeRequest(frame, chunks.get(0));
renderProbeResponse(frame, chunks.get(1));
@@ -896,7 +897,7 @@ class HttpTab implements MonitorTab {
int row = area.top() + 1;
// Method selector
- renderProbeLabel(frame, innerX, row, labelW, "Method:", probeField == PROBE_METHOD);
+ FormHelper.renderLabel(frame, innerX, row, labelW, "Method:", probeField == PROBE_METHOD);
Rect methodArea = new Rect(innerX + labelW, row, fieldW, 1);
Style methodSt = methodStyle(method);
String leftArr = probeField == PROBE_METHOD ? "◀ " : " ";
@@ -908,7 +909,7 @@ class HttpTab implements MonitorTab {
// Full URL (read-only)
row++;
- renderProbeLabel(frame, innerX, row, labelW, "URL:", false);
+ FormHelper.renderLabel(frame, innerX, row, labelW, "URL:", false);
String fullUrl = (probeBaseUrl != null ? probeBaseUrl : "") + probePathState.text();
Rect urlArea = new Rect(innerX + labelW, row, fieldW, 1);
frame.renderWidget(Paragraph.from(Line.from(
@@ -916,7 +917,7 @@ class HttpTab implements MonitorTab {
// Path input
row++;
- renderProbeLabel(frame, innerX, row, labelW, "Path:", probeField == PROBE_PATH);
+ FormHelper.renderLabel(frame, innerX, row, labelW, "Path:", probeField == PROBE_PATH);
Rect pathArea = new Rect(innerX + labelW, row, fieldW, 1);
if (probeField == PROBE_PATH && !probeSending.get()) {
TextInput textInput = TextInput.builder().cursorStyle(Style.EMPTY.reversed()).build();
@@ -937,18 +938,18 @@ class HttpTab implements MonitorTab {
row++;
boolean isSelected = probeField == PROBE_HEADERS && probeSelectedHeader == i;
String label = i == 0 ? "Headers:" : "";
- renderProbeLabel(frame, innerX, row, labelW, label,
+ FormHelper.renderLabel(frame, innerX, row, labelW, label,
isSelected || (i == 0 && probeField == PROBE_HEADERS));
- HeaderEntry he = probeHeaders.get(i);
+ FormHelper.HeaderEntry he = probeHeaders.get(i);
int fieldX = innerX + labelW;
Rect keyArea = new Rect(fieldX, row, keyW, 1);
if (isSelected && probeEditingHeaderKey && !probeSending.get()) {
TextInput keyInput = TextInput.builder().cursorStyle(Style.EMPTY.reversed()).build();
- frame.renderStatefulWidget(keyInput, keyArea, he.keyInput);
+ frame.renderStatefulWidget(keyInput, keyArea, he.keyInput());
} else {
- String keyText = he.keyInput.text();
+ String keyText = he.keyInput().text();
Style keyStyle = keyText.isEmpty() ? Style.EMPTY.dim()
: isSelected ? Style.EMPTY.bold() : Style.EMPTY;
frame.renderWidget(Paragraph.from(Line.from(
@@ -962,9 +963,9 @@ class HttpTab implements MonitorTab {
Rect valArea = new Rect(fieldX + keyW + 3, row, valW, 1);
if (isSelected && !probeEditingHeaderKey && !probeSending.get()) {
TextInput valInput = TextInput.builder().cursorStyle(Style.EMPTY.reversed()).build();
- frame.renderStatefulWidget(valInput, valArea, he.valueInput);
+ frame.renderStatefulWidget(valInput, valArea, he.valueInput());
} else {
- String valText = he.valueInput.text();
+ String valText = he.valueInput().text();
Style valStyle = valText.isEmpty() ? Style.EMPTY.dim()
: isSelected ? Style.EMPTY.bold() : Style.EMPTY;
frame.renderWidget(Paragraph.from(Line.from(
@@ -975,7 +976,7 @@ class HttpTab implements MonitorTab {
// Body input
row++;
- renderProbeLabel(frame, innerX, row, labelW, "Body:", probeField == PROBE_BODY);
+ FormHelper.renderLabel(frame, innerX, row, labelW, "Body:", probeField == PROBE_BODY);
Rect bodyArea = new Rect(innerX + labelW, row, fieldW, 1);
if (probeField == PROBE_BODY && !probeSending.get()) {
TextInput textInput = TextInput.builder()
@@ -1095,7 +1096,7 @@ class HttpTab implements MonitorTab {
String statusStr = entry.error ? "ERR" : entry.statusText;
String elapsedStr = entry.elapsed > 0 ? entry.elapsed + "ms" : "";
String bodySnippet = entry.body != null && !entry.body.isEmpty()
- ? " " + truncate(entry.body, 30)
+ ? " " + TuiHelper.truncate(entry.body, 30)
: "";
Style lineStyle = selected ? Style.EMPTY.bold() : Style.EMPTY;
@@ -1116,12 +1117,6 @@ class HttpTab implements MonitorTab {
area);
}
- private void renderProbeLabel(Frame frame, int x, int y, int w, String label, boolean selected) {
- Style style = selected ? Style.EMPTY.bold() : Style.EMPTY.dim();
- Rect labelArea = new Rect(x, y, w, 1);
- frame.renderWidget(Paragraph.from(Line.from(Span.styled(label, style))), labelArea);
- }
-
private static Style statusStyle(String status) {
if (status == null) {
return Style.EMPTY.bold();
@@ -1545,31 +1540,6 @@ class HttpTab implements MonitorTab {
return MonitorContext.sortStyle(column, sort);
}
- private static void handleTextInput(KeyEvent ke, TextInputState state) {
- if (ke.isDeleteBackward()) {
- state.deleteBackward();
- } else if (ke.isDeleteForward()) {
- state.deleteForward();
- } else if (ke.isLeft()) {
- state.moveCursorLeft();
- } else if (ke.isRight()) {
- state.moveCursorRight();
- } else if (ke.isHome()) {
- state.moveCursorToStart();
- } else if (ke.isEnd()) {
- state.moveCursorToEnd();
- } else if (ke.code() == KeyCode.CHAR) {
- state.insert(ke.character());
- }
- }
-
- private static String truncate(String s, int max) {
- if (s == null) {
- return "";
- }
- return s.length() <= max ? s : s.substring(0, max - 1) + "…";
- }
-
@Override
public SelectionContext getSelectionContext() {
IntegrationInfo info = ctx.findSelectedIntegration();
@@ -1584,12 +1554,9 @@ class HttpTab implements MonitorTab {
return new SelectionContext("table", items, sel != null ? sel : -1, items.size(), "HTTP");
}
- record HeaderEntry(TextInputState keyInput, TextInputState valueInput) {
- }
-
record ProbeHistoryEntry(
String method, String path,
- List<HeaderEntry> headers, String body,
+ List<FormHelper.HeaderEntry> headers, String body,
int statusCode, long elapsed,
String statusText, boolean error) {
}
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
index 1201b06fa9e4..4c31ff7a5ce3 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
@@ -19,15 +19,19 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
+import dev.tamboui.layout.Constraint;
+import dev.tamboui.layout.Layout;
import dev.tamboui.layout.Rect;
import dev.tamboui.style.Color;
import dev.tamboui.style.Style;
import dev.tamboui.terminal.Frame;
import dev.tamboui.text.Line;
import dev.tamboui.text.Span;
+import dev.tamboui.text.Text;
import dev.tamboui.tui.event.KeyCode;
import dev.tamboui.tui.event.KeyEvent;
import dev.tamboui.widgets.Clear;
@@ -37,16 +41,22 @@ import dev.tamboui.widgets.block.Title;
import dev.tamboui.widgets.input.TextInput;
import dev.tamboui.widgets.input.TextInputState;
import dev.tamboui.widgets.paragraph.Paragraph;
+import org.apache.camel.dsl.jbang.core.common.CamelCommandHelper;
import org.apache.camel.dsl.jbang.core.common.PathUtils;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
+import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*;
+
class SendMessagePopup {
private static final int FIELD_ROUTE = 0;
private static final int FIELD_BODY = 1;
private static final int FIELD_HEADERS = 2;
private static final int FIELD_MODE = 3;
+ private static final int FIELD_HISTORY = 4;
+
+ private static final int MAX_HISTORY = 20;
private boolean visible;
private boolean sending;
@@ -57,13 +67,26 @@ class SendMessagePopup {
private final TextInputState bodyState = new TextInputState("");
private int selectedField = FIELD_BODY;
private boolean inOut;
- private String resultMessage;
- private boolean resultError;
- private List<HeaderEntry> headers;
+ private List<FormHelper.HeaderEntry> headers;
private int selectedHeader;
private boolean editingHeaderKey;
+ // Response state
+ private String responseStatus;
+ private long responseElapsed;
+ private String responseExchangeId;
+ private List<String> responseHeaderLines;
+ private String responseRawBody;
+ private List<String> responseLines;
+ private boolean responseError;
+ private int responseScroll;
+ private boolean prettyPrint;
+
+ // History
+ private final List<SendHistoryEntry> history = new ArrayList<>();
+ private int historyIndex;
+
boolean isVisible() {
return visible;
}
@@ -79,12 +102,12 @@ class SendMessagePopup {
this.bodyState.clear();
this.selectedField = FIELD_BODY;
this.inOut = false;
- this.resultMessage = null;
- this.resultError = false;
this.sending = false;
this.headers = null;
this.selectedHeader = 0;
this.editingHeaderKey = true;
+ clearResponse();
+ this.historyIndex = 0;
this.visible = true;
}
@@ -104,8 +127,32 @@ class SendMessagePopup {
return true;
}
if (ke.isConfirm()) {
+ if (selectedField == FIELD_HISTORY && !history.isEmpty()) {
+ replayHistoryEntry(history.get(historyIndex));
+ }
+ return true;
+ }
+
+ // PgUp/PgDn scroll response
+ if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
+ responseScroll = Math.max(0, responseScroll - 10);
return true;
}
+ if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
+ responseScroll += 10;
+ return true;
+ }
+
+ // Toggle pretty print (only when not editing text)
+ if (ke.isChar('p') && selectedField != FIELD_BODY && selectedField != FIELD_HEADERS) {
+ prettyPrint = !prettyPrint;
+ if (responseLines != null) {
+ rebuildResponseLines();
+ responseScroll = 0;
+ }
+ return true;
+ }
+
if (selectedField == FIELD_BODY) {
if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
if (hasHeaders()) {
@@ -120,10 +167,16 @@ class SendMessagePopup {
if (ke.isUp()) {
if (routes.size() > 1) {
selectedField = FIELD_ROUTE;
+ } else {
+ goToLastField();
}
return true;
}
- handleTextInput(ke, bodyState);
+ if (ke.isChar('+')) {
+ addHeader();
+ return true;
+ }
+ FormHelper.handleTextInput(ke, bodyState);
return true;
}
if (selectedField == FIELD_ROUTE) {
@@ -132,7 +185,7 @@ class SendMessagePopup {
return true;
}
if (ke.isUp()) {
- selectedField = FIELD_MODE;
+ goToLastField();
return true;
}
if (ke.isLeft()) {
@@ -150,7 +203,10 @@ class SendMessagePopup {
}
if (selectedField == FIELD_MODE) {
if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
- if (routes.size() > 1) {
+ if (!history.isEmpty()) {
+ selectedField = FIELD_HISTORY;
+ historyIndex = 0;
+ } else if (routes.size() > 1) {
selectedField = FIELD_ROUTE;
} else {
selectedField = FIELD_BODY;
@@ -177,12 +233,46 @@ class SendMessagePopup {
}
return true;
}
+ if (selectedField == FIELD_HISTORY) {
+ if (ke.isKey(KeyCode.TAB)) {
+ if (routes.size() > 1) {
+ selectedField = FIELD_ROUTE;
+ } else {
+ selectedField = FIELD_BODY;
+ }
+ return true;
+ }
+ if (ke.isUp()) {
+ if (historyIndex > 0) {
+ historyIndex--;
+ } else {
+ selectedField = FIELD_MODE;
+ }
+ return true;
+ }
+ if (ke.isDown()) {
+ if (historyIndex < history.size() - 1) {
+ historyIndex++;
+ }
+ return true;
+ }
+ return true;
+ }
return true;
}
+ private void goToLastField() {
+ if (!history.isEmpty()) {
+ selectedField = FIELD_HISTORY;
+ historyIndex = 0;
+ } else {
+ selectedField = FIELD_MODE;
+ }
+ }
+
private boolean handleHeaderKeyEvent(KeyEvent ke) {
- HeaderEntry current = headers.get(selectedHeader);
- TextInputState activeInput = editingHeaderKey ? current.keyInput : current.valueInput;
+ FormHelper.HeaderEntry current = headers.get(selectedHeader);
+ TextInputState activeInput = editingHeaderKey ? current.keyInput() : current.valueInput();
if (ke.isChar('+')) {
addHeader();
@@ -213,7 +303,7 @@ class SendMessagePopup {
return true;
}
if (ke.isDeleteBackward()) {
- if (editingHeaderKey && current.keyInput.text().isEmpty()) {
+ if (editingHeaderKey && current.keyInput().text().isEmpty()) {
headers.remove(selectedHeader);
if (headers.isEmpty()) {
headers = null;
@@ -265,7 +355,7 @@ class SendMessagePopup {
if (headers == null) {
headers = new ArrayList<>();
}
- headers.add(new HeaderEntry(new TextInputState(""), new TextInputState("")));
+ headers.add(new FormHelper.HeaderEntry(new TextInputState(""), new TextInputState("")));
selectedField = FIELD_HEADERS;
selectedHeader = headers.size() - 1;
editingHeaderKey = true;
@@ -276,18 +366,10 @@ class SendMessagePopup {
}
void handlePaste(String text) {
- if (!visible || sending || text == null || text.isEmpty()) {
+ if (!visible || sending) {
return;
}
- TextInputState target = activeTextInput();
- if (target != null) {
- for (int i = 0; i < text.length(); i++) {
- char ch = text.charAt(i);
- if (ch != '\n' && ch != '\r') {
- target.insert(ch);
- }
- }
- }
+ FormHelper.handlePaste(text, activeTextInput());
}
private TextInputState activeTextInput() {
@@ -295,8 +377,8 @@ class SendMessagePopup {
return bodyState;
}
if (selectedField == FIELD_HEADERS && hasHeaders()) {
- HeaderEntry he = headers.get(selectedHeader);
- return editingHeaderKey ? he.keyInput : he.valueInput;
+ FormHelper.HeaderEntry he = headers.get(selectedHeader);
+ return editingHeaderKey ? he.keyInput() : he.valueInput();
}
return null;
}
@@ -306,8 +388,9 @@ class SendMessagePopup {
return;
}
sending = true;
- resultMessage = "Sending...";
- resultError = false;
+ responseStatus = "Sending...";
+ responseError = false;
+ responseScroll = 0;
String body = bodyState.text();
if (body != null && body.startsWith("file:")) {
@@ -321,7 +404,9 @@ class SendMessagePopup {
String endpoint = route.routeId;
String mep = inOut ? "InOut" : "InOnly";
String targetPid = pid;
- List<HeaderEntry> hdrs = headers != null ? new ArrayList<>(headers) : null;
+ boolean captureInOut = inOut;
+ List<FormHelper.HeaderEntry> hdrs = headers != null ? new ArrayList<>(headers) : null;
+ String routeId = route.routeId;
scheduler.execute(() -> {
try {
@@ -338,9 +423,9 @@ class SendMessagePopup {
if (hdrs != null && !hdrs.isEmpty()) {
JsonArray arr = new JsonArray();
- for (HeaderEntry he : hdrs) {
- String k = he.keyInput.text().trim();
- String v = he.valueInput.text();
+ for (FormHelper.HeaderEntry he : hdrs) {
+ String k = he.keyInput().text().trim();
+ String v = he.valueInput().text();
if (!k.isEmpty()) {
JsonObject jo = new JsonObject();
jo.put("key", k);
@@ -359,86 +444,269 @@ class SendMessagePopup {
JsonObject response = MonitorContext.pollJsonResponse(outputFile, 25000);
PathUtils.deleteFile(outputFile);
- if (response != null) {
- String status = response.getString("status");
- Object elapsed = response.get("elapsed");
- if ("success".equals(status)) {
- String msg = "Sent (" + elapsed + "ms)";
- JsonObject message = response.getMap("message");
- if (inOut && message != null) {
- String replyBody = objToString(message.get("body"));
- if (replyBody != null && !replyBody.isEmpty()) {
- msg += " - Reply: " + truncate(replyBody, 40);
+ if (response == null) {
+ applyResult(routeId, sendBody, hdrs, captureInOut,
+ null, 0, null, null, null,
+ true, "No response from integration");
+ return;
+ }
+
+ String status = response.getString("status");
+ long elapsed = TuiHelper.objToLong(response.get("elapsed"));
+ String exchangeId = response.getString("exchangeId");
+
+ if ("success".equals(status)) {
+ List<String> hdrLines = new ArrayList<>();
+ String rawBody = null;
+
+ if (exchangeId != null) {
+ hdrLines.add("exchangeId: " + exchangeId);
+ }
+ hdrLines.add("exchangePattern: " + mep);
+
+ JsonObject message = response.getMap("message");
+ if (captureInOut && message != null) {
+ // Parse exchange headers
+ Collection<JsonObject> msgHeaders = message.getCollection("headers");
+ if (msgHeaders != null) {
+ for (JsonObject h : msgHeaders) {
+ String k = h.getString("key");
+ Object v = h.get("value");
+ if (k != null) {
+ hdrLines.add(k + ": " + (v != null ? v.toString() : ""));
+ }
}
}
- resultMessage = msg;
- resultError = false;
- } else {
- JsonObject exception = response.getMap("exception");
- if (exception != null) {
- String exMsg = exception.getString("message");
- resultMessage = "Error: " + (exMsg != null ? truncate(exMsg, 50) : status);
- } else {
- resultMessage = "Error: " + (status != null ? status : "unknown");
+
+ // Parse body
+ JsonObject bodyObj = message.getMap("body");
+ if (bodyObj != null) {
+ Object bodyValue = bodyObj.get("value");
+ if (bodyValue != null) {
+ rawBody = bodyValue.toString();
+ }
}
- resultError = true;
}
+
+ applyResult(routeId, sendBody, hdrs, captureInOut,
+ "success", elapsed, exchangeId, hdrLines, rawBody,
+ false, null);
} else {
- resultMessage = "No response from integration";
- resultError = true;
+ List<String> errLines = new ArrayList<>();
+ JsonObject exception = response.getMap("exception");
+ String errMsg;
+ if (exception != null) {
+ errMsg = exception.getString("message");
+ if (errMsg == null) {
+ errMsg = status != null ? status : "unknown error";
+ }
+ errLines.add("Exception: " + errMsg);
+ String stackTrace = exception.getString("stackTrace");
+ if (stackTrace != null) {
+ for (String line : stackTrace.split("\n")) {
+ errLines.add(line);
+ }
+ }
+ } else {
+ errMsg = status != null ? status : "unknown error";
+ errLines.add("Error: " + errMsg);
+ }
+
+ applyResult(routeId, sendBody, hdrs, captureInOut,
+ status, elapsed, exchangeId, errLines, null,
+ true, errMsg);
}
} catch (Exception e) {
- resultMessage = "Error: " + e.getMessage();
- resultError = true;
+ applyResult(routeId, sendBody, hdrs, captureInOut,
+ null, 0, null, null, null,
+ true, "Error: " + e.getMessage());
} finally {
sending = false;
}
});
}
+ private void applyResult(
+ String routeId, String body, List<FormHelper.HeaderEntry> hdrs, boolean wasInOut,
+ String status, long elapsed, String exchangeId,
+ List<String> hdrLines, String rawBody,
+ boolean error, String errorMessage) {
+
+ // Build history entry
+ List<FormHelper.HeaderEntry> histHeaders = null;
+ if (hdrs != null && !hdrs.isEmpty()) {
+ histHeaders = new ArrayList<>();
+ for (FormHelper.HeaderEntry he : hdrs) {
+ histHeaders.add(new FormHelper.HeaderEntry(
+ new TextInputState(he.keyInput().text()),
+ new TextInputState(he.valueInput().text())));
+ }
+ }
+ SendHistoryEntry histEntry = new SendHistoryEntry(
+ routeId, body, histHeaders, wasInOut,
+ status != null ? status : (error ? "failed" : "unknown"),
+ elapsed, error);
+
+ responseStatus = status;
+ responseElapsed = elapsed;
+ responseExchangeId = exchangeId;
+ responseError = error;
+ responseScroll = 0;
+
+ if (error && hdrLines == null) {
+ responseHeaderLines = null;
+ responseRawBody = null;
+ responseLines = errorMessage != null ? List.of(errorMessage) : List.of("Unknown error");
+ } else {
+ responseHeaderLines = hdrLines;
+ responseRawBody = rawBody;
+ rebuildResponseLines();
+ }
+
+ history.add(0, histEntry);
+ if (history.size() > MAX_HISTORY) {
+ history.remove(history.size() - 1);
+ }
+ historyIndex = 0;
+ }
+
+ private void replayHistoryEntry(SendHistoryEntry entry) {
+ // Restore route selection
+ if (entry.routeId != null) {
+ for (int i = 0; i < routes.size(); i++) {
+ if (entry.routeId.equals(routes.get(i).routeId)) {
+ selectedRouteIndex = i;
+ break;
+ }
+ }
+ }
+
+ // Restore body
+ bodyState.clear();
+ if (entry.body != null) {
+ for (int i = 0; i < entry.body.length(); i++) {
+ bodyState.insert(entry.body.charAt(i));
+ }
+ }
+
+ // Restore headers
+ headers = null;
+ if (entry.headers != null) {
+ for (FormHelper.HeaderEntry he : entry.headers) {
+ addHeaderWithValues(he.keyInput().text(), he.valueInput().text());
+ }
+ }
+
+ // Restore mode
+ inOut = entry.inOut;
+ selectedField = FIELD_BODY;
+ }
+
+ private void addHeaderWithValues(String key, String value) {
+ if (headers == null) {
+ headers = new ArrayList<>();
+ }
+ TextInputState keyState = new TextInputState("");
+ TextInputState valState = new TextInputState("");
+ for (int i = 0; i < key.length(); i++) {
+ keyState.insert(key.charAt(i));
+ }
+ for (int i = 0; i < value.length(); i++) {
+ valState.insert(value.charAt(i));
+ }
+ headers.add(new FormHelper.HeaderEntry(keyState, valState));
+ }
+
+ private void clearResponse() {
+ responseStatus = null;
+ responseElapsed = 0;
+ responseExchangeId = null;
+ responseHeaderLines = null;
+ responseRawBody = null;
+ responseLines = null;
+ responseError = false;
+ responseScroll = 0;
+ }
+
+ private void rebuildResponseLines() {
+ List<String> lines = new ArrayList<>();
+ if (responseHeaderLines != null) {
+ lines.addAll(responseHeaderLines);
+ }
+ if (responseRawBody != null && !responseRawBody.isEmpty()) {
+ lines.add("");
+ String body = responseRawBody;
+ if (prettyPrint) {
+ body = CamelCommandHelper.valueAsStringPretty(body, false);
+ }
+ for (String line : body.split("\n", -1)) {
+ lines.add(line);
+ }
+ }
+ responseLines = lines;
+ }
+
+ // ---- Rendering ----
+
void render(Frame frame, Rect area) {
if (!visible) {
return;
}
+ frame.renderWidget(Clear.INSTANCE, area);
+
+ int padX = 2;
+ int padY = 1;
+ Rect inner = new Rect(
+ area.left() + padX, area.top() + padY,
+ area.width() - padX * 2, area.height() - padY * 2);
+
int headerCount = hasHeaders() ? headers.size() : 0;
- int popupW = Math.min(80, area.width() - 4);
- int baseH = routes.size() > 1 ? 14 : 12;
- int popupH = baseH + (headerCount > 0 ? headerCount + 1 : 0);
- int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
- int y = area.top() + 2;
- Rect popup = new Rect(x, y, Math.min(popupW, area.width()), Math.min(popupH, area.height() - 2));
+ int requestHeight = 7 + headerCount + (headerCount > 0 ? 1 : 0);
+ if (routes.size() > 1) {
+ requestHeight += 1;
+ }
- frame.renderWidget(Clear.INSTANCE, popup);
+ int historyHeight = Math.min(6, history.size() + 2);
+ if (historyHeight < 3) {
+ historyHeight = 3;
+ }
+ List<Rect> chunks = Layout.vertical()
+ .constraints(
+ Constraint.length(requestHeight),
+ Constraint.fill(),
+ Constraint.length(historyHeight))
+ .split(inner);
+
+ renderRequest(frame, chunks.get(0));
+ renderResponse(frame, chunks.get(1));
+ renderHistory(frame, chunks.get(2));
+ }
+
+ private void renderRequest(Frame frame, Rect area) {
String title = " Send Message";
if (integrationName != null) {
- title += " - " + truncate(integrationName, 20);
+ title += " — " + TuiHelper.truncate(integrationName, 30);
}
title += " ";
+
Block block = Block.builder()
.borderType(BorderType.ROUNDED)
.title(Title.from(Line.from(Span.styled(title, Style.EMPTY.fg(Color.YELLOW).bold()))))
- .titleBottom(Title.from(Line.from(
- Span.styled(" +", MonitorContext.HINT_KEY_STYLE),
- Span.raw(" header │"),
- Span.styled(" Enter", MonitorContext.HINT_KEY_STYLE),
- Span.raw(" send │"),
- Span.styled(" Esc", MonitorContext.HINT_KEY_STYLE),
- Span.raw(" close "))))
.build();
- frame.renderWidget(block, popup);
+ frame.renderWidget(block, area);
+ Rect inner = block.inner(area);
- int innerX = popup.left() + 2;
- int innerW = popup.width() - 4;
+ int innerX = inner.left() + 1;
+ int innerW = inner.width() - 2;
int labelW = 8;
int fieldW = innerW - labelW;
- int row = popup.top() + 1;
+ int row = inner.top();
// Route selector (only if multiple routes)
if (routes.size() > 1) {
- row++;
- renderLabel(frame, innerX, row, labelW, "Route:", selectedField == FIELD_ROUTE);
+ FormHelper.renderLabel(frame, innerX, row, labelW, "Route:", selectedField == FIELD_ROUTE);
RouteInfo ri = routes.get(selectedRouteIndex);
String routeDisplay = ri.routeId + " (" + truncateUri(ri.from, fieldW - ri.routeId.length() - 6) + ")";
String arrow = selectedField == FIELD_ROUTE ? "◀ " : " ";
@@ -449,11 +717,12 @@ class SendMessagePopup {
Span.styled(arrow, routeStyle),
Span.styled(routeDisplay, routeStyle),
Span.styled(arrowR, routeStyle))), routeArea);
+ row++;
}
// Body input
- row += 2;
- renderLabel(frame, innerX, row, labelW, "Body:", selectedField == FIELD_BODY);
+ row++;
+ FormHelper.renderLabel(frame, innerX, row, labelW, "Body:", selectedField == FIELD_BODY);
Rect bodyArea = new Rect(innerX + labelW, row, fieldW, 1);
if (selectedField == FIELD_BODY && !sending) {
TextInput textInput = TextInput.builder()
@@ -470,17 +739,16 @@ class SendMessagePopup {
// Headers section
if (hasHeaders()) {
- row++;
int keyW = Math.min(20, fieldW / 3);
int valW = fieldW - keyW - 3;
for (int i = 0; i < headers.size(); i++) {
row++;
boolean isSelected = selectedField == FIELD_HEADERS && selectedHeader == i;
String label = i == 0 ? "Hdrs:" : "";
- renderLabel(frame, innerX, row, labelW, label,
+ FormHelper.renderLabel(frame, innerX, row, labelW, label,
isSelected || (i == 0 && selectedField == FIELD_HEADERS));
- HeaderEntry he = headers.get(i);
+ FormHelper.HeaderEntry he = headers.get(i);
int fieldX = innerX + labelW;
// Key field
@@ -489,9 +757,9 @@ class SendMessagePopup {
TextInput keyInput = TextInput.builder()
.cursorStyle(Style.EMPTY.reversed())
.build();
- frame.renderStatefulWidget(keyInput, keyArea, he.keyInput);
+ frame.renderStatefulWidget(keyInput, keyArea, he.keyInput());
} else {
- String keyText = he.keyInput.text();
+ String keyText = he.keyInput().text();
Style keyStyle;
if (keyText.isEmpty()) {
keyStyle = Style.EMPTY.dim();
@@ -514,9 +782,9 @@ class SendMessagePopup {
TextInput valInput = TextInput.builder()
.cursorStyle(Style.EMPTY.reversed())
.build();
- frame.renderStatefulWidget(valInput, valArea, he.valueInput);
+ frame.renderStatefulWidget(valInput, valArea, he.valueInput());
} else {
- String valText = he.valueInput.text();
+ String valText = he.valueInput().text();
Style valStyle;
if (valText.isEmpty()) {
valStyle = Style.EMPTY.dim();
@@ -532,7 +800,7 @@ class SendMessagePopup {
// Mode toggle
row += 2;
- renderLabel(frame, innerX, row, labelW, "Mode:", selectedField == FIELD_MODE);
+ FormHelper.renderLabel(frame, innerX, row, labelW, "Mode:", selectedField == FIELD_MODE);
Rect modeArea = new Rect(innerX + labelW, row, fieldW, 1);
Style inOnlyStyle = !inOut ? Style.EMPTY.bold().reversed() : Style.EMPTY.dim();
Style inOutStyle = inOut ? Style.EMPTY.bold().reversed() : Style.EMPTY.dim();
@@ -540,19 +808,155 @@ class SendMessagePopup {
Span.styled(" InOnly ", inOnlyStyle),
Span.raw(" "),
Span.styled(" InOut ", inOutStyle))), modeArea);
+ }
- // Result line
- if (resultMessage != null) {
- row += 2;
- Style resultStyle = resultError
- ? Style.EMPTY.fg(Color.LIGHT_RED)
- : Style.EMPTY.fg(Color.GREEN);
- Rect resultArea = new Rect(innerX, row, innerW, 1);
- frame.renderWidget(Paragraph.from(Line.from(
- Span.styled(resultMessage, resultStyle))), resultArea);
+ private void renderResponse(Frame frame, Rect area) {
+ String titleStr;
+ Style titleStyle = Style.EMPTY.bold();
+
+ if (responseStatus == null && !sending) {
+ titleStr = " Response ";
+ } else if (sending) {
+ titleStr = " Sending... ";
+ titleStyle = Style.EMPTY.fg(Color.YELLOW).bold();
+ } else if (responseError) {
+ titleStr = " Response — Error ";
+ if (responseElapsed > 0) {
+ titleStr = " Response — Error (" + responseElapsed + "ms) ";
+ }
+ titleStyle = Style.EMPTY.fg(Color.LIGHT_RED).bold();
+ } else {
+ titleStr = " Response — " + (inOut ? "InOut" : "InOnly");
+ if (responseElapsed > 0) {
+ titleStr += " (" + responseElapsed + "ms)";
+ }
+ titleStr += " ";
+ titleStyle = Style.EMPTY.fg(Color.GREEN).bold();
+ }
+
+ Title title = Title.from(Line.from(Span.styled(titleStr, titleStyle)));
+
+ if (responseLines == null || responseLines.isEmpty()) {
+ String placeholder = responseStatus == null && !sending
+ ? " Press Enter to send message"
+ : " No response content";
+ frame.renderWidget(
+ Paragraph.builder()
+ .text(Text.from(Line.from(Span.styled(placeholder, Style.EMPTY.dim()))))
+ .block(Block.builder().borderType(BorderType.ROUNDED).title(title).build())
+ .build(),
+ area);
+ return;
+ }
+
+ int visibleLines = area.height() - 2;
+ if (visibleLines < 1) {
+ visibleLines = 1;
+ }
+ int maxScroll = Math.max(0, responseLines.size() - visibleLines);
+ responseScroll = Math.min(responseScroll, maxScroll);
+
+ int end = Math.min(responseScroll + visibleLines, responseLines.size());
+ List<Line> lines = new ArrayList<>();
+ for (int i = responseScroll; i < end; i++) {
+ String line = responseLines.get(i);
+ if (line.isEmpty()) {
+ lines.add(Line.from(Span.raw("")));
+ } else if (line.contains(": ") && !line.startsWith(" ") && !line.startsWith("{")
+ && !line.startsWith("[") && !line.startsWith("\"")) {
+ int colon = line.indexOf(": ");
+ lines.add(Line.from(
+ Span.styled(line.substring(0, colon + 1), Style.EMPTY.fg(Color.YELLOW)),
+ Span.raw(line.substring(colon + 1))));
+ } else {
+ lines.add(Line.from(Span.raw(line)));
+ }
+ }
+
+ frame.renderWidget(
+ Paragraph.builder()
+ .text(Text.from(lines))
+ .block(Block.builder().borderType(BorderType.ROUNDED).title(title).build())
+ .build(),
+ area);
+ }
+
+ private void renderHistory(Frame frame, Rect area) {
+ String title = " History [" + history.size() + "] ";
+
+ if (history.isEmpty()) {
+ frame.renderWidget(
+ Paragraph.builder()
+ .text(Text.from(Line.from(
+ Span.styled(" No messages sent yet", Style.EMPTY.dim()))))
+ .block(Block.builder().borderType(BorderType.ROUNDED).title(title).build())
+ .build(),
+ area);
+ return;
+ }
+
+ int visibleLines = area.height() - 2;
+ if (visibleLines < 1) {
+ visibleLines = 1;
+ }
+
+ int start = 0;
+ if (historyIndex >= visibleLines) {
+ start = historyIndex - visibleLines + 1;
+ }
+ int end = Math.min(start + visibleLines, history.size());
+
+ List<Line> lines = new ArrayList<>();
+ for (int i = start; i < end; i++) {
+ SendHistoryEntry entry = history.get(i);
+ boolean selected = selectedField == FIELD_HISTORY && i == historyIndex;
+ String pointer = selected ? "► " : " ";
+ String routeStr = String.format("%-16s", entry.routeId != null ? entry.routeId : "");
+ String modeStr = entry.inOut ? "InOut " : "InOnly";
+ String statusStr = entry.error ? "ERR" : entry.status;
+ String elapsedStr = entry.elapsed > 0 ? entry.elapsed + "ms" : "";
+ String bodySnippet = entry.body != null && !entry.body.isEmpty()
+ ? " " + TuiHelper.truncate(entry.body, 30)
+ : "";
+
+ Style lineStyle = selected ? Style.EMPTY.bold() : Style.EMPTY;
+ Style statusStyle = entry.error ? Style.EMPTY.fg(Color.LIGHT_RED) : Style.EMPTY.fg(Color.GREEN);
+ if (!selected) {
+ statusStyle = statusStyle.dim();
+ }
+
+ lines.add(Line.from(
+ Span.styled(pointer, lineStyle),
+ Span.styled(routeStr, lineStyle),
+ Span.styled(modeStr + " ", Style.EMPTY.dim()),
+ Span.styled(statusStr, statusStyle),
+ Span.styled(" " + elapsedStr, Style.EMPTY.dim()),
+ Span.styled(bodySnippet, Style.EMPTY.dim())));
+ }
+
+ frame.renderWidget(
+ Paragraph.builder()
+ .text(Text.from(lines))
+ .block(Block.builder().borderType(BorderType.ROUNDED).title(title).build())
+ .build(),
+ area);
+ }
+
+ void renderFooter(List<Span> spans) {
+ hint(spans, "Esc", "back");
+ hint(spans, "Tab", "fields");
+ hint(spans, "Enter", "send");
+ hint(spans, "+", "header");
+ hint(spans, "p", "pretty" + (prettyPrint ? " [on]" : ""));
+ if (!history.isEmpty()) {
+ hintLast(spans, "↑↓", "history");
+ } else {
+ hintLast(spans, "PgUp/Dn", "scroll");
}
}
+ // ---- Helpers ----
+
private int findSmartDefault(String preSelectRouteId) {
if (preSelectRouteId != null) {
for (int i = 0; i < routes.size(); i++) {
@@ -571,53 +975,19 @@ class SendMessagePopup {
return 0;
}
- private void handleTextInput(KeyEvent ke, TextInputState state) {
- if (ke.isDeleteBackward()) {
- state.deleteBackward();
- } else if (ke.isDeleteForward()) {
- state.deleteForward();
- } else if (ke.isLeft()) {
- state.moveCursorLeft();
- } else if (ke.isRight()) {
- state.moveCursorRight();
- } else if (ke.isHome()) {
- state.moveCursorToStart();
- } else if (ke.isEnd()) {
- state.moveCursorToEnd();
- } else if (ke.code() == KeyCode.CHAR) {
- state.insert(ke.character());
- }
- }
-
- private void renderLabel(Frame frame, int x, int y, int w, String label, boolean selected) {
- Style style = selected ? Style.EMPTY.bold() : Style.EMPTY.dim();
- Rect labelArea = new Rect(x, y, w, 1);
- frame.renderWidget(Paragraph.from(Line.from(Span.styled(label, style))), labelArea);
- }
-
- private static String truncate(String s, int max) {
- if (s == null) {
- return "";
- }
- return s.length() <= max ? s : s.substring(0, max - 1) + "…";
- }
-
private static String truncateUri(String uri, int max) {
if (uri == null) {
return "";
}
int q = uri.indexOf('?');
String clean = q > 0 ? uri.substring(0, q) : uri;
- return truncate(clean, max);
- }
-
- private static String objToString(Object obj) {
- if (obj == null) {
- return null;
- }
- return obj.toString();
+ return TuiHelper.truncate(clean, max);
}
- record HeaderEntry(TextInputState keyInput, TextInputState valueInput) {
+ record SendHistoryEntry(
+ String routeId, String body,
+ List<FormHelper.HeaderEntry> headers,
+ boolean inOut, String status,
+ long elapsed, boolean error) {
}
}
(camel) 04/06: CAMEL-23648: camel-jbang - Sync bundled examples from camel-jbang-examples repo
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch CAMEL-23648-tui-send-fullscreen
in repository https://gitbox.apache.org/repos/asf/camel.git
commit ae7dd51df3b7fe1e201269da2484cd8bfb4f0b67
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sun May 31 09:33:09 2026 +0200
CAMEL-23648: camel-jbang - Sync bundled examples from camel-jbang-examples repo
Extend the sync-example-catalog Maven profile to also download source
files for all bundled examples from apache/camel-jbang-examples. Add
examples/**/* excludes to both RAT and mycila license plugins so synced
files don't need license headers.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
dsl/camel-jbang/camel-jbang-core/pom.xml | 55 ++++++++++-
.../resources/examples/circuit-breaker/README.md | 48 ++++++++--
.../src/main/resources/examples/cron-log/README.md | 6 +-
.../examples/cron-log/application.properties | 2 +
.../examples/cron-log/cron-log.camel.yaml | 6 +-
.../src/main/resources/examples/groovy/README.md | 38 +++++++-
.../resources/examples/groovy/groovy.camel.yaml | 31 +++----
.../examples/message-size/orders.camel.yaml | 102 +++++++++++++++++++++
.../src/main/resources/examples/rest-api/README.md | 6 +-
.../examples/rest-api/rest-api.camel.yaml | 26 ++++--
.../src/main/resources/examples/routes/README.md | 49 ++++++++--
.../resources/examples/routes/routes.camel.yaml | 11 ++-
.../main/resources/examples/timer-log/README.md | 4 +-
.../examples/timer-log/timer-log.camel.yaml | 11 ++-
.../resources/examples/tui-hello-world/README.md | 21 +++++
.../src/main/resources/examples/xslt/README.md | 50 ++++++++--
.../resources/examples/xslt/consumer.camel.yaml | 6 +-
pom.xml | 2 +
18 files changed, 394 insertions(+), 80 deletions(-)
diff --git a/dsl/camel-jbang/camel-jbang-core/pom.xml b/dsl/camel-jbang/camel-jbang-core/pom.xml
index e066af7906a9..b0b9f0c33b89 100644
--- a/dsl/camel-jbang/camel-jbang-core/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-core/pom.xml
@@ -273,9 +273,60 @@
</goals>
<configuration>
<target>
- <get src="https://raw.githubusercontent.com/apache/camel-jbang-examples/main/camel-jbang-example-catalog.json"
- dest="${project.basedir}/src/main/resources/examples/camel-jbang-example-catalog.json"
+ <property name="examples.base" value="https://raw.githubusercontent.com/apache/camel-jbang-examples/main"/>
+ <property name="examples.dest" value="${project.basedir}/src/main/resources/examples"/>
+ <!-- Catalog -->
+ <get src="${examples.base}/camel-jbang-example-catalog.json"
+ dest="${examples.dest}/camel-jbang-example-catalog.json"
usetimestamp="true"/>
+ <!-- Bundled examples -->
+ <macrodef name="sync-example">
+ <attribute name="name"/>
+ <attribute name="file"/>
+ <sequential>
+ <mkdir dir="${examples.dest}/@{name}"/>
+ <get src="${examples.base}/@{name}/@{file}"
+ dest="${examples.dest}/@{name}/@{file}"
+ usetimestamp="true"/>
+ </sequential>
+ </macrodef>
+ <!-- circuit-breaker -->
+ <sync-example name="circuit-breaker" file="README.md"/>
+ <sync-example name="circuit-breaker" file="route.camel.yaml"/>
+ <!-- cron-log -->
+ <sync-example name="cron-log" file="README.md"/>
+ <sync-example name="cron-log" file="application.properties"/>
+ <sync-example name="cron-log" file="cron-log.camel.yaml"/>
+ <!-- groovy -->
+ <sync-example name="groovy" file="README.md"/>
+ <sync-example name="groovy" file="application.properties"/>
+ <sync-example name="groovy" file="groovy.camel.yaml"/>
+ <!-- message-size -->
+ <sync-example name="message-size" file="README.md"/>
+ <sync-example name="message-size" file="orders.camel.yaml"/>
+ <!-- rest-api -->
+ <sync-example name="rest-api" file="README.md"/>
+ <sync-example name="rest-api" file="application.properties"/>
+ <sync-example name="rest-api" file="rest-api.camel.yaml"/>
+ <!-- routes -->
+ <sync-example name="routes" file="Greeter.java"/>
+ <sync-example name="routes" file="README.md"/>
+ <sync-example name="routes" file="application.properties"/>
+ <sync-example name="routes" file="beans.yaml"/>
+ <sync-example name="routes" file="routes.camel.yaml"/>
+ <!-- timer-log -->
+ <sync-example name="timer-log" file="README.md"/>
+ <sync-example name="timer-log" file="application.properties"/>
+ <sync-example name="timer-log" file="timer-log.camel.yaml"/>
+ <!-- tui-hello-world -->
+ <sync-example name="tui-hello-world" file="README.md"/>
+ <sync-example name="tui-hello-world" file="tui-hello-world.yaml"/>
+ <!-- xslt -->
+ <sync-example name="xslt" file="README.md"/>
+ <sync-example name="xslt" file="consumer.camel.yaml"/>
+ <mkdir dir="${examples.dest}/xslt/input"/>
+ <sync-example name="xslt" file="input/account.xml"/>
+ <sync-example name="xslt" file="stylesheet.xsl"/>
</target>
</configuration>
</execution>
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/README.md
index 9bfb59c16757..2cf9f71fc207 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/README.md
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/README.md
@@ -1,35 +1,55 @@
-# Circuit Breaker
+## Circuit Breaker
This example shows how Camel JBang can use circuit breaker EIP.
-## How to run
+### Install JBang
+
+First install JBang according to https://www.jbang.dev
+
+When JBang is installed then you should be able to run from a shell:
+
+```sh
+$ jbang --version
+```
+
+This will output the version of JBang.
+
+To run this example you can either install Camel on JBang via:
+
+```sh
+$ jbang app install camel@apache/camel
+```
+
+Which allows to run Camel JBang with `camel` as shown below.
+
+### How to run
You can run this example using:
```sh
-camel run *
+$ camel run *
```
While the Camel integration is running, then from another terminal type:
```sh
-camel get circuit-breaker
+$ camel get circuit-breaker
```
Which then output the state of the circuit breaker. You can run this command with `--watch` and see
how the state of the circuit breaker changes from closed to open due to many failures.
```sh
-camel get circuit-breaker --watch
+$ camel get circuit-breaker --watch
```
-## Inspecting errors
+### Inspecting errors
Because the circuit breaker triggers exceptions, you can use `camel get error` to inspect
captured routing errors:
```sh
-camel get error
+$ camel get error
```
This shows a summary table with PID, route, node, exchange ID, exception type and message.
@@ -37,11 +57,21 @@ This shows a summary table with PID, route, node, exchange ID, exception type an
To see full details of the last error (body, headers, variables, properties, exception and message history):
```sh
-camel get error --last
+$ camel get error --last
```
You can also pick a specific error by its exchange ID:
```sh
-camel get error --id=<exchangeId> --detail
+$ camel get error --id=<exchangeId> --detail
```
+
+### Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/README.md
index 6bf8e7b5681f..33b5d10a4906 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/README.md
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/README.md
@@ -1,7 +1,7 @@
-# Cron Log
+## Cron Log
-This example shows a scheduled task using a cron expression that logs the current time every 5 seconds.
+This example shows a scheduled task that logs the current time every 5 seconds.
-## How to run
+### How to run
camel run cron-log.camel.yaml
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/application.properties
index 9194b486f064..a60bfaac62a6 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/application.properties
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/application.properties
@@ -14,5 +14,7 @@
## See the License for the specific language governing permissions and
## limitations under the License.
## ---------------------------------------------------------------------------
+# Timer period in milliseconds
+cron.period=5000
# Log message prefix
cron.message=Scheduled task running at
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/cron-log.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/cron-log.camel.yaml
index b2ad3ff59b74..c67fe724179f 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/cron-log.camel.yaml
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/cron-log.camel.yaml
@@ -18,9 +18,10 @@
- route:
id: cron-log
from:
- uri: cron:tab
+ uri: timer
parameters:
- schedule: "0/5 * * * * ?"
+ timerName: cron
+ period: "{{cron.period}}"
steps:
- setBody:
expression:
@@ -28,3 +29,4 @@
expression: "{{cron.message}} ${date:now:HH:mm:ss}"
- log:
message: "${body}"
+
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/README.md
index 46dcc024a0bb..8fbe350f9191 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/README.md
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/README.md
@@ -1,4 +1,4 @@
-# Groovy
+## Groovy
This example shows how to use Groovy with extra dependencies in Camel JBang.
@@ -12,12 +12,32 @@ The extra dependency is declared in `application.properties` using the
camel.jbang.dependencies=commons-validator:commons-validator:1.10.1
```
-## How to run
+### Install JBang
+
+First install JBang according to https://www.jbang.dev
+
+When JBang is installed then you should be able to run from a shell:
+
+```sh
+$ jbang --version
+```
+
+This will output the version of JBang.
+
+To run this example you can either install Camel on JBang via:
+
+```sh
+$ jbang app install camel@apache/camel
+```
+
+Which allows to run Camel JBang with `camel` as shown below.
+
+### How to run
You can run this example using:
```sh
-camel run *
+$ camel run *
```
To see the invalid email branch, edit `groovy.camel.yaml` and change the `contactEmail` header in the `once` URI to an invalid value:
@@ -35,5 +55,15 @@ You can also declare dependencies as a modeline comment at the top of the YAML r
Or pass the dependency on the command line:
```sh
-camel run groovy.camel.yaml --dep=commons-validator:commons-validator:1.10.1
+$ camel run groovy.camel.yaml --dep=commons-validator:commons-validator:1.10.1
```
+
+### Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/groovy.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/groovy.camel.yaml
index 2b89a31150bb..055e708c4240 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/groovy.camel.yaml
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/groovy.camel.yaml
@@ -16,34 +16,27 @@
#
- route:
- id: route-1681
from:
- id: from-1133
uri: once
parameters:
name: validate
steps:
- setHeader:
- id: setHeader-4262
- constant:
- expression: user@example.com
name: contactEmail
+ expression:
+ constant:
+ expression: user@example.com
- choice:
- id: choice-1178
- otherwise:
- id: otherwise-1234
- steps:
- - log:
- id: log-3646
- message: "Invalid email: ${header.contactEmail}"
when:
- - id: when-1441
+ - expression:
+ groovy:
+ expression: |-
+ import org.apache.commons.validator.routines.EmailValidator
+ EmailValidator.getInstance().isValid(request.headers['contactEmail'])
steps:
- log:
- id: log-2349
message: "Valid contact: ${header.contactEmail}"
- groovy:
- expression: >-
- import org.apache.commons.validator.routines.EmailValidator
-
- EmailValidator.getInstance().isValid(request.headers['contactEmail'])
+ otherwise:
+ steps:
+ - log:
+ message: "Invalid email: ${header.contactEmail}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/message-size/orders.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/message-size/orders.camel.yaml
new file mode 100644
index 000000000000..9b2e0361c3a4
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/message-size/orders.camel.yaml
@@ -0,0 +1,102 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Producers: simulate different payload sizes using Content-Length header
+- route:
+ id: small-producer
+ from:
+ uri: timer:small
+ parameters:
+ period: 2000
+ steps:
+ - setHeader:
+ name: Content-Length
+ simple: "${random(100,1023)}"
+ - setHeader:
+ name: source
+ simple: small-producer
+ - to:
+ uri: seda:small
+
+- route:
+ id: medium-producer
+ from:
+ uri: timer:medium
+ parameters:
+ period: 3000
+ steps:
+ - setHeader:
+ name: Content-Length
+ simple: "${random(8192,18432)}"
+ - setHeader:
+ name: source
+ simple: medium-producer
+ - setHeader:
+ name: tracking-id
+ simple: "TRK-${random(10000,99999)}"
+ - to:
+ uri: seda:medium
+
+- route:
+ id: large-producer
+ from:
+ uri: timer:large
+ parameters:
+ period: 5000
+ steps:
+ - setHeader:
+ name: Content-Length
+ simple: "${random(10485760,20971520)}"
+ - setHeader:
+ name: source
+ simple: large-producer
+ - setHeader:
+ name: tracking-id
+ simple: "TRK-${random(10000,99999)}"
+ - setHeader:
+ name: batch-id
+ simple: "BATCH-${random(100,999)}"
+ - setHeader:
+ name: priority
+ simple: "${random(1,5)}"
+ - to:
+ uri: seda:large
+
+# Consumers: process messages from each seda endpoint
+- route:
+ id: process-small
+ from:
+ uri: seda:small
+ steps:
+ - log:
+ message: "Small: Content-Length=${header.Content-Length}"
+
+- route:
+ id: process-medium
+ from:
+ uri: seda:medium
+ steps:
+ - log:
+ message: "Medium: Content-Length=${header.Content-Length}"
+
+- route:
+ id: process-large
+ from:
+ uri: seda:large
+ steps:
+ - log:
+ message: "Large: Content-Length=${header.Content-Length}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/README.md
index 9ade804eab16..db3dac32b83c 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/README.md
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/README.md
@@ -1,12 +1,12 @@
-# REST API
+## REST API
This example shows a REST API with hello endpoints.
-## How to run
+### How to run
camel run rest-api.camel.yaml
-## Try it
+### Try it
curl http://localhost:8080/api/hello
curl http://localhost:8080/api/hello/World
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/rest-api.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/rest-api.camel.yaml
index 8bf9e16560e0..0482fc3c0b2d 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/rest-api.camel.yaml
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/rest-api.camel.yaml
@@ -17,22 +17,30 @@
- rest:
path: /api
- get:
- - path: /hello
- to: direct:hello
- - path: /hello/{name}
- to: direct:hello-name
+ steps:
+ - get:
+ path: /hello
+ - get:
+ path: "/hello/{name}"
- route:
id: hello
from:
- uri: direct:hello
+ uri: direct
+ parameters:
+ name: hello
steps:
- setBody:
- constant: "{{greeting.message}}"
+ expression:
+ constant:
+ expression: "{{greeting.message}}"
- route:
id: hello-name
from:
- uri: direct:hello-name
+ uri: direct
+ parameters:
+ name: hello-name
steps:
- setBody:
- simple: "Hello ${header.name} from Camel REST API!"
+ expression:
+ simple:
+ expression: "Hello ${header.name} from Camel REST API!"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/README.md
index 7dc86fc5a31c..16620aabaf08 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/README.md
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/README.md
@@ -1,41 +1,72 @@
-# Routes
+## Routes
This example shows how routes are defined in Yaml.
-## How to run
+### Install JBang
+
+First install JBang according to https://www.jbang.dev
+
+When JBang is installed then you should be able to run from a shell:
+
+```sh
+$ jbang --version
+```
+
+This will output the version of JBang.
+
+To run this example you can either install Camel on JBang via:
+
+```sh
+$ jbang app install camel@apache/camel
+```
+
+Which allows to run Camel JBang with `camel` as shown below.
+
+### How to run
You can run this example using:
```sh
-camel run *
+$ camel run *
```
Camel will start a route that periodically provides a greeting message.
-## Live reload
+### Live reload
You can run the example in dev mode which allows you to edit the example,
and hot-reload when the file is saved.
```sh
-camel run * --dev
+$ camel run * --dev
```
-## Run directly from GitHub
+### Run directly from GitHub
The example can also be run directly by referring to the GitHub URL as shown:
```sh
-camel run https://github.com/apache/camel-jbang-examples/tree/main/routes
+$ camel run https://github.com/apache/camel-jbang-examples/tree/main/routes
```
-## Developer Web Console
+### Developer Web Console
You can enable the developer console via `--console` flag as show:
```sh
-camel run * --console
+$ camel run * --console
```
Then you can browse: http://localhost:8080/q/dev to introspect the running Camel Application.
Under "beans" Camel should display bean `greeter`.
+
+
+### Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/routes.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/routes.camel.yaml
index a1bfb7dcb291..b22631a8265a 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/routes.camel.yaml
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/routes.camel.yaml
@@ -18,12 +18,17 @@
- route:
id: greeting-route
from:
- uri: timer:start
+ uri: timer
parameters:
+ timerName: start
period: 1000
steps:
- setBody:
- simple: I'm ${routeId}
+ expression:
+ simple:
+ expression: "I'm ${routeId}"
- bean:
ref: greeter
- - log: ${body}
+ - log:
+ message: "${body}"
+
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/README.md
index fd5b8ac6199f..f773ce1bad55 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/README.md
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/README.md
@@ -1,7 +1,7 @@
-# Timer Log
+## Timer Log
This example shows a simple timer that logs a hello message every second.
-## How to run
+### How to run
camel run timer-log.camel.yaml
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/timer-log.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/timer-log.camel.yaml
index 6a40ad378d2e..291f22895d28 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/timer-log.camel.yaml
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/timer-log.camel.yaml
@@ -18,10 +18,15 @@
- route:
id: timer-log
from:
- uri: timer:tick
+ uri: timer
parameters:
+ timerName: tick
period: "{{timer.period}}"
steps:
- setBody:
- simple: "{{greeting.message}}"
- - log: "${body}"
+ expression:
+ simple:
+ expression: "{{greeting.message}}"
+ - log:
+ message: "${body}"
+
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/tui-hello-world/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/tui-hello-world/README.md
new file mode 100644
index 000000000000..faac9275543c
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/tui-hello-world/README.md
@@ -0,0 +1,21 @@
+## TUI Hello World
+
+A simple example to try the TUI Send Message feature.
+
+On startup, a welcome message is logged once. The `greet` route listens on `direct:greet` and replies with a greeting.
+
+### How to run
+
+ camel run tui-hello-world.yaml
+
+### Send a message via TUI
+
+ camel tui
+
+Select the integration, press `F2`, choose `Send Message`, type your name (e.g. `World`), and press Enter.
+
+Toggle the mode to `InOut` to see the reply (`Hello World!`).
+
+### Send a message via CLI
+
+ camel cmd send tui-hello-world --endpoint=direct:greet --body=World
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/README.md
index ec989f624348..0b84da586f8c 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/README.md
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/README.md
@@ -1,31 +1,61 @@
-# XSLT Transformation
+## XSLT Transformation
This example shows a basic XML transformation using XSLT style sheet.
-## How to run
+### Install JBang
-You can run this example using:
+First install JBang according to https://www.jbang.dev
+
+When JBang is installed then you should be able to run from a shell:
```sh
-camel run *
+$ jbang --version
```
-This reads the XML input file from `./input/account.xml` and applies XSL transformation.
+This will output the version of JBang.
-## Live updates of message transformation
+To run this example you can either install Camel on JBang via:
-You can do live changes to the stylesheet and see the output in real-time with Camel JBang by running:
+```sh
+$ jbang app install camel@apache/camel
+```
+
+Which allows to run Camel JBang with `camel` as shown below.
+
+### How to run
+
+Then you can run this example using:
```sh
-camel transform message --body=file:input/account.xml --component=xslt --template=file:stylesheet.xsl --pretty --watch
+$ camel run *
+```
+
+This reads the XML input file from _./input/account.xml_ and applies XSL transformation.
+
+### Live updates of message transformation
+
+You can do live changes to the stylesheet and see the output in real-time with Camel JBang by running:
+
+```bash
+$ camel transform message --body=file:input/account.xml --component=xslt --template=file:stylesheet.xsl --pretty --watch
```
You can then edit the `stylesheet.xsl` file, and save the file, and watch the terminal for updated result.
-## Run directly from GitHub
+### Run directly from GitHub
The example can also be run directly by referring to the GitHub URL as shown:
```sh
-camel run https://github.com/apache/camel-jbang-examples/tree/main/xslt
+$ camel run https://github.com/apache/camel-jbang-examples/tree/main/xslt
```
+
+### Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/consumer.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/consumer.camel.yaml
index 18c6663e29aa..9e2e1ca556e6 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/consumer.camel.yaml
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/consumer.camel.yaml
@@ -17,8 +17,9 @@
- route:
from:
- uri: file://input
+ uri: file
parameters:
+ directoryName: input
fileName: account.xml
noop: true
steps:
@@ -27,4 +28,5 @@
parameters:
resourceUri: stylesheet.xsl
- log:
- message: Transformed data:\n ${prettyBody}
+ message: "Transformed data:\\n ${prettyBody}"
+
diff --git a/pom.xml b/pom.xml
index 3ee598aa57de..16fdc581bae4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -390,6 +390,7 @@
<exclude>**/javax.annotations.processing.Processor</exclude>
<exclude>**/helm/etc/**</exclude>
<exclude>**/examples/*</exclude>
+ <exclude>**/examples/**/*</exclude>
<exclude>**/*.wav</exclude>
<exclude>**/*.cast</exclude>
</excludes>
@@ -588,6 +589,7 @@
<exclude>**/.camel-jbang/**/*</exclude>
<exclude>**/commands/edit/edit-help.txt</exclude>
<exclude>**/examples/*</exclude>
+ <exclude>**/examples/**/*</exclude>
<exclude>**/src/main/resources/testdata/**/*</exclude>
<!-- Claude Code / OMC worktrees -->
<exclude>.claude/**/*</exclude>
(camel) 03/06: CAMEL-23648: camel-jbang - TUI add Reset Screen action to F2 menu
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch CAMEL-23648-tui-send-fullscreen
in repository https://gitbox.apache.org/repos/asf/camel.git
commit db250d885475a071d7e08f2bfb123bef3f27a110
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sun May 31 09:24:12 2026 +0200
CAMEL-23648: camel-jbang - TUI add Reset Screen action to F2 menu
Add a Reset Screen action to the F2 actions menu (Diagnostics group)
that clears the terminal and resets TamboUI buffers, forcing a full
redraw. Useful when the screen gets garbled from external interference.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../dsl/jbang/core/commands/tui/ActionsPopup.java | 30 +++++++++++++++-------
.../dsl/jbang/core/commands/tui/CamelMonitor.java | 1 +
2 files changed, 22 insertions(+), 9 deletions(-)
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
index cc8f3b4dfaf0..830bdfbccb6e 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
@@ -76,18 +76,19 @@ class ActionsPopup {
private static final int ACTION_DOCTOR = 4;
private static final int ACTION_CLASSPATH = 5;
private static final int ACTION_RESET_STATS = 6;
- private static final int ACTION_STOP_ALL = 7;
+ private static final int ACTION_RESET_SCREEN = 7;
+ private static final int ACTION_STOP_ALL = 8;
// Group 3: Recording & Presentation
- private static final int ACTION_SCREENSHOT = 8;
- private static final int ACTION_TAPE_RECORDING = 9;
- private static final int ACTION_TAPE_INSTRUCTIONS = 10;
- private static final int ACTION_CAPTION = 11;
- private static final int ACTION_SHOW_KEYSTROKES = 12;
+ private static final int ACTION_SCREENSHOT = 9;
+ private static final int ACTION_TAPE_RECORDING = 10;
+ private static final int ACTION_TAPE_INSTRUCTIONS = 11;
+ private static final int ACTION_CAPTION = 12;
+ private static final int ACTION_SHOW_KEYSTROKES = 13;
// Group 4: MCP
- private static final int ACTION_MCP_INFO = 13;
- private static final int ACTION_MCP_LOG = 14;
+ private static final int ACTION_MCP_INFO = 14;
+ private static final int ACTION_MCP_LOG = 15;
- private static final int[] GROUP_SIZES = { 4, 4, 5 };
+ private static final int[] GROUP_SIZES = { 4, 5, 5 };
private static final int MCP_GROUP_SIZE = 2;
private final Supplier<Set<String>> runningNames;
@@ -98,6 +99,7 @@ class ActionsPopup {
private final Supplier<Boolean> keystrokesEnabled;
private final Runnable toggleTapeRecording;
private Runnable resetStatsAction;
+ private Runnable resetScreenAction;
private final Supplier<Boolean> tapeRecordingActive;
private MonitorContext ctx;
private boolean mcpEnabled;
@@ -182,6 +184,10 @@ class ActionsPopup {
this.resetStatsAction = resetStatsAction;
}
+ void setResetScreenAction(Runnable resetScreenAction) {
+ this.resetScreenAction = resetScreenAction;
+ }
+
void setMcpEnabled(
boolean enabled, int port, Supplier<String> connectedClient, Supplier<List<TuiMcpServer.LogEntry>> activityLog) {
this.mcpEnabled = enabled;
@@ -304,6 +310,7 @@ class ActionsPopup {
labels.add("Run Doctor");
labels.add("Show Classpath");
labels.add("Reset Stats");
+ labels.add("Reset Screen");
labels.add("Stop All");
labels.add("───");
// Group 3: Recording & Presentation
@@ -531,6 +538,11 @@ class ActionsPopup {
if (resetStatsAction != null) {
resetStatsAction.run();
}
+ } else if (action == ACTION_RESET_SCREEN) {
+ showActionsMenu = false;
+ if (resetScreenAction != null) {
+ resetScreenAction.run();
+ }
} else if (action == ACTION_STOP_ALL) {
showActionsMenu = false;
stopAllPopup.open();
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 33dbb3c5faf6..200addfe5e0f 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
@@ -357,6 +357,7 @@ public class CamelMonitor extends CamelCommand {
this.runner = tui;
ctx.runner = tui;
actionsPopup.setScheduler(tui.scheduler());
+ actionsPopup.setResetScreenAction(() -> tui.terminal().clear());
// 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());
(camel) 05/06: CAMEL-23648: camel-jbang - TUI fix Reset Screen menu item rendering
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch CAMEL-23648-tui-send-fullscreen
in repository https://gitbox.apache.org/repos/asf/camel.git
commit ccbbc340f31500a9d0d3f54532a56009b4e46cb8
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sun May 31 09:40:34 2026 +0200
CAMEL-23648: camel-jbang - TUI fix Reset Screen menu item rendering
Fix missing Reset Screen entry in visual menu list, use dynamic
visualActionCount instead of hardcoded sums, and use 🧹 emoji
instead of 🖥️ which has a variation selector causing width mismatch.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../camel/dsl/jbang/core/commands/tui/ActionsPopup.java | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
index 830bdfbccb6e..204783e8b2a6 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
@@ -198,13 +198,16 @@ class ActionsPopup {
}
private int visualActionCount() {
+ int total = 0;
+ for (int gs : GROUP_SIZES) {
+ total += gs;
+ }
+ int dividers = GROUP_SIZES.length - 1;
if (mcpEnabled) {
- // 4 + 4 + 5 + 2 actions = 15, plus 3 dividers = 18
- return 15 + 3;
- } else {
- // 4 + 4 + 5 actions = 13, plus 2 dividers = 15
- return 13 + 2;
+ total += MCP_GROUP_SIZE;
+ dividers++;
}
+ return total + dividers;
}
private boolean isDividerIndex(int visualIndex) {
@@ -708,6 +711,7 @@ class ActionsPopup {
items.add(ListItem.from(" 🩺 Run Doctor"));
items.add(ListItem.from(" 📦 Show Classpath"));
items.add(ListItem.from(" 🔄 Reset Stats"));
+ items.add(ListItem.from(" 🧹 Reset Screen"));
items.add(ListItem.from(stopLabel));
items.add(ListItem.from(divider).style(Style.EMPTY.dim()));
// Group 3: Recording & Presentation