This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch topology-external-endpoints in repository https://gitbox.apache.org/repos/asf/camel.git
commit 4d117003520694aceae4226d79c88e083144bbca Author: Claus Ibsen <[email protected]> AuthorDate: Tue Jun 2 12:54:22 2026 +0200 Add stub checkbox and failure log dialog to TUI run options Add a "Stub (no Docker needed)" checkbox to the run options form that passes --stub=all and skips infra service startup. When an example launch fails, show the full log output in a scrollable dialog instead of a brief notification. Use TuiHelper.ansiToLine for proper ANSI/control character handling and Overflow.CLIP to prevent rendering artifacts. Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../dsl/jbang/core/commands/tui/ActionsPopup.java | 65 +++++++++++++++++----- .../jbang/core/commands/tui/RunOptionsForm.java | 32 ++++++++--- 2 files changed, 74 insertions(+), 23 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 9c660417f9f9..1fab0476680f 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 @@ -35,6 +35,7 @@ import java.util.stream.Collectors; import dev.tamboui.layout.Rect; import dev.tamboui.markdown.MarkdownView; import dev.tamboui.style.Color; +import dev.tamboui.style.Overflow; import dev.tamboui.style.Style; import dev.tamboui.terminal.Frame; import dev.tamboui.text.Line; @@ -847,9 +848,18 @@ class ActionsPopup { private void renderDocViewer(Frame frame, Rect area) { frame.renderWidget(Clear.INSTANCE, area); Rect popup = new Rect(area.left() + 2, area.top() + 1, area.width() - 4, area.height() - 2); + Title title; + if (docTitle != null && docTitle.startsWith("Failed:")) { + String rest = docTitle.substring("Failed:".length()); + title = Title.from(Line.from( + Span.styled(" Failed:", Style.EMPTY.fg(Color.LIGHT_RED).bold()), + Span.raw(rest + " "))); + } else { + title = Title.from(" " + docTitle + " "); + } Block block = Block.builder() .borderType(BorderType.ROUNDED) - .title(" " + docTitle + " ") + .title(title) .titleBottom(Title.from(Line.from( Span.styled(" ↑↓", MonitorContext.HINT_KEY_STYLE), Span.raw(" scroll │"), Span.styled(" Esc", MonitorContext.HINT_KEY_STYLE), Span.raw(" back ")))) @@ -861,9 +871,13 @@ class ActionsPopup { int totalLines = docLines.size(); int clampedScroll = Math.min(docScroll, Math.max(0, totalLines - visibleLines)); int end = Math.min(clampedScroll + visibleLines, totalLines); - List<Line> visible = docLines.subList(clampedScroll, end); + List<Line> visible = new ArrayList<>(docLines.subList(clampedScroll, end)); + while (visible.size() < visibleLines) { + visible.add(Line.from("")); + } frame.renderWidget( - Paragraph.builder().text(Text.from(visible.toArray(Line[]::new))).build(), + Paragraph.builder().text(Text.from(visible.toArray(Line[]::new))) + .overflow(Overflow.CLIP).build(), inner); } else { MarkdownView view = MarkdownView.builder() @@ -1333,16 +1347,19 @@ class ActionsPopup { displayName = exampleName; } List<String> extraArgs = runOptionsForm.buildArgs(); + boolean stub = runOptionsForm.isStubMode(); runOptionsForm.close(); - List<String> missing = findMissingInfraServices(selectedExample); - if (!missing.isEmpty()) { - if (!isContainerRuntimeAvailable()) { - setNotification("Docker/Podman required for infra services. Run Doctor for details", true); + if (!stub) { + List<String> missing = findMissingInfraServices(selectedExample); + if (!missing.isEmpty()) { + if (!isContainerRuntimeAvailable()) { + setNotification("Docker/Podman required for infra services. Run Doctor for details", true); + return; + } + startMissingInfraAndDeferExample(missing, exampleName, displayName, extraArgs); return; } - startMissingInfraAndDeferExample(missing, exampleName, displayName, extraArgs); - return; } doLaunchExample(exampleName, displayName, extraArgs); @@ -1916,11 +1933,7 @@ class ActionsPopup { launchNotificationError = false; launchNotificationExpiry = now + 5000; } else { - String detail = readFirstLine(pl.outputFile()); - launchNotification = "Failed: " + pl.name() - + (detail != null ? " - " + detail : ""); - launchNotificationError = true; - launchNotificationExpiry = now + 10000; + showFailureLog(pl.name(), pl.outputFile()); } it.remove(); } else if (now - pl.startTime() > 8000) { @@ -1932,6 +1945,30 @@ class ActionsPopup { } } + private void showFailureLog(String name, Path logFile) { + List<String> logLines = readAllLines(logFile); + if (logLines.isEmpty()) { + setNotification("Failed: " + name + " (no output)", true); + return; + } + docTitle = "Failed: " + name; + docContent = null; + docLines = logLines.stream() + .map(line -> TuiHelper.ansiToLine(line.replace("\t", " "), 0)) + .collect(Collectors.toList()); + docScroll = 0; + showDocViewer = true; + docViewerFromExampleBrowser = false; + } + + private static List<String> readAllLines(Path file) { + try { + return Files.readAllLines(file); + } catch (IOException e) { + return List.of(); + } + } + // ---- Utilities ---- private static String readFirstLine(Path file) { diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java index c481f6b030cd..12b4a05df719 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java @@ -49,7 +49,8 @@ class RunOptionsForm { private static final int ROW_DEV = 3; private static final int ROW_OBSERVE = 4; private static final int ROW_TRACE = 5; - private static final int ROW_COUNT = 6; + private static final int ROW_STUB = 6; + private static final int ROW_COUNT = 7; private boolean visible; private int page; @@ -68,6 +69,7 @@ class RunOptionsForm { private boolean devMode; private boolean observe; private boolean backlogTrace; + private boolean stubMode; private String exampleTitle; @@ -92,6 +94,7 @@ class RunOptionsForm { devMode = dev; observe = false; backlogTrace = false; + stubMode = false; selectedRow = ROW_NAME; page = PAGE_OPTIONS; selectedProperty = 0; @@ -108,6 +111,10 @@ class RunOptionsForm { return nameInput != null ? nameInput.text().trim() : ""; } + boolean isStubMode() { + return stubMode; + } + boolean handleKeyEvent(KeyEvent ke) { if (!visible) { return false; @@ -182,6 +189,9 @@ class RunOptionsForm { if (backlogTrace) { args.add("--backlog-trace"); } + if (stubMode) { + args.add("--stub=all"); + } if (properties != null) { for (PropertyEntry pe : properties) { String current = pe.valueInput().text(); @@ -205,7 +215,7 @@ class RunOptionsForm { return true; } if (ke.isDown()) { - if (selectedRow == ROW_TRACE && hasProperties()) { + if (selectedRow == ROW_STUB && hasProperties()) { page = PAGE_PROPERTIES; selectedProperty = 0; } else { @@ -214,7 +224,7 @@ class RunOptionsForm { return true; } if (ke.isFocusNext()) { - if (selectedRow == ROW_TRACE && hasProperties()) { + if (selectedRow == ROW_STUB && hasProperties()) { page = PAGE_PROPERTIES; selectedProperty = 0; } else { @@ -226,7 +236,7 @@ class RunOptionsForm { selectedRow = (selectedRow - 1 + ROW_COUNT) % ROW_COUNT; return true; } - if (ke.isRight() && hasProperties() && selectedRow >= ROW_DEV) { + if (ke.isRight() && hasProperties() && selectedRow >= ROW_STUB) { page = PAGE_PROPERTIES; selectedProperty = 0; return true; @@ -250,6 +260,7 @@ class RunOptionsForm { case ROW_DEV -> devMode = !devMode; case ROW_OBSERVE -> observe = !observe; case ROW_TRACE -> backlogTrace = !backlogTrace; + case ROW_STUB -> stubMode = !stubMode; } return true; } @@ -276,7 +287,7 @@ class RunOptionsForm { editingKey = false; if (selectedProperty == 0) { page = PAGE_OPTIONS; - selectedRow = ROW_TRACE; + selectedRow = ROW_STUB; } else { selectedProperty--; } @@ -306,7 +317,7 @@ class RunOptionsForm { } else if (selectedProperty == 0) { editingKey = false; page = PAGE_OPTIONS; - selectedRow = ROW_TRACE; + selectedRow = ROW_STUB; } else { editingKey = false; selectedProperty--; @@ -322,7 +333,7 @@ class RunOptionsForm { return true; } page = PAGE_OPTIONS; - selectedRow = ROW_TRACE; + selectedRow = ROW_STUB; editingKey = false; return true; } @@ -337,7 +348,7 @@ class RunOptionsForm { } if (properties.isEmpty()) { page = PAGE_OPTIONS; - selectedRow = ROW_TRACE; + selectedRow = ROW_STUB; } return true; } @@ -365,7 +376,7 @@ class RunOptionsForm { private void renderOptionsPage(Frame frame, Rect area) { int popupW = Math.min(56, area.width() - 4); - int popupH = 10; + int popupH = 11; int x = area.left() + Math.max(0, (area.width() - popupW) / 2); int y = area.top() + Math.max(0, (area.height() - popupH) / 2); Rect popup = new Rect(x, y, Math.min(popupW, area.width()), Math.min(popupH, area.height())); @@ -427,6 +438,9 @@ class RunOptionsForm { rowY++; renderCheckbox(frame, innerX, rowY, innerW, "Backlog trace", backlogTrace, selectedRow == ROW_TRACE); + rowY++; + + renderCheckbox(frame, innerX, rowY, innerW, "Stub (no Docker needed)", stubMode, selectedRow == ROW_STUB); } private void renderPropertiesPage(Frame frame, Rect area) {
