This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-23615-tui-cold-restart in repository https://gitbox.apache.org/repos/asf/camel.git
commit 6cbf0473a52273c6a2c79e8e127536b0cb1e18d4 Author: Claus Ibsen <[email protected]> AuthorDate: Thu May 28 11:00:14 2026 +0200 CAMEL-23615: camel-jbang - TUI cold restart for integrations Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../dsl/jbang/core/commands/tui/CamelMonitor.java | 141 +++++++++++++++++++++ .../jbang/core/commands/tui/IntegrationInfo.java | 1 + 2 files changed, 142 insertions(+) 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 d24b31eb7f5a..ba7e66f43ab0 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 @@ -16,6 +16,7 @@ */ package org.apache.camel.dsl.jbang.core.commands.tui; +import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.charset.StandardCharsets; @@ -37,8 +38,10 @@ import java.util.Map; import java.util.Optional; import java.util.Queue; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -220,6 +223,9 @@ public class CamelMonitor extends CamelCommand { private volatile long lastRefresh; private boolean showKillConfirm; + private String monitorNotification; + private boolean monitorNotificationError; + private long monitorNotificationExpiry; private volatile Buffer lastBuffer; private volatile long renderGeneration; private volatile String screenshotMessage; @@ -622,6 +628,11 @@ public class CamelMonitor extends CamelCommand { showKillConfirm = true; return true; } + // Overview tab: cold restart (stop + re-launch) for selected integration + if (tab == TAB_OVERVIEW && ke.isChar('r') && ctx.selectedPid != null && !ctx.infraTableFocused) { + restartSelectedProcess(); + return true; + } // Delegate remaining keys to active tab if (activeTab != null && activeTab.handleKeyEvent(ke)) { return true; @@ -918,6 +929,15 @@ public class CamelMonitor extends CamelCommand { Style style = actionsPopup.notificationError() ? Style.EMPTY.fg(Color.RED) : Style.EMPTY.fg(Color.GREEN); titleSpans.add(Span.styled(actionsPopup.notification(), style)); } + if (monitorNotification != null) { + if (System.currentTimeMillis() > monitorNotificationExpiry) { + monitorNotification = null; + } else { + titleSpans.add(Span.raw(" ")); + Style style = monitorNotificationError ? Style.EMPTY.fg(Color.RED) : Style.EMPTY.fg(Color.GREEN); + titleSpans.add(Span.styled(monitorNotification, style)); + } + } Line titleLine = Line.from(titleSpans); frame.renderWidget( @@ -1654,6 +1674,123 @@ public class CamelMonitor extends CamelCommand { } } + private void restartSelectedProcess() { + if (ctx.selectedPid == null || ctx.infraTableFocused) { + return; + } + long pid; + try { + pid = Long.parseLong(ctx.selectedPid); + } catch (NumberFormatException e) { + return; + } + IntegrationInfo info = findSelectedIntegration(); + if (info == null) { + return; + } + ProcessHandle ph = ProcessHandle.of(pid).orElse(null); + if (ph == null) { + return; + } + + // capture command line before stopping + Optional<String> cmdOpt = ph.info().command(); + Optional<String[]> argsOpt = ph.info().arguments(); + Optional<String> cmdLineOpt = ph.info().commandLine(); + + String name = info.name; + String directory = info.directory; + + // remember name so the restarted process gets auto-selected + ctx.lastSelectedName = name; + + // stop gracefully + ph.destroy(); + setNotification("Restarting: " + name, false); + + // re-launch in background after process terminates + if (runner != null) { + runner.scheduler().execute(() -> { + try { + // wait for termination (max 10 seconds, then force kill) + CompletableFuture<ProcessHandle> exitFuture = ph.onExit().toCompletableFuture(); + try { + exitFuture.get(10, TimeUnit.SECONDS); + } catch (Exception e) { + ph.destroyForcibly(); + Thread.sleep(500); + } + + // build command + List<String> cmd = new ArrayList<>(); + if (cmdOpt.isPresent() && argsOpt.isPresent() && argsOpt.get().length > 0) { + cmd.add(cmdOpt.get()); + Collections.addAll(cmd, argsOpt.get()); + } else if (cmdLineOpt.isPresent()) { + cmd.addAll(parseCommandLine(cmdLineOpt.get())); + } + + if (cmd.isEmpty()) { + runner.runOnRenderThread( + () -> setNotification("Cannot restart: command line not available", true)); + return; + } + + ProcessBuilder pb = new ProcessBuilder(cmd); + if (directory != null) { + pb.directory(new File(directory)); + } + pb.redirectErrorStream(true); + Path outputFile = Files.createTempFile("camel-restart-", ".log"); + outputFile.toFile().deleteOnExit(); + pb.redirectOutput(outputFile.toFile()); + pb.start(); + + runner.runOnRenderThread(() -> setNotification("Restarted: " + name, false)); + } catch (Exception e) { + runner.runOnRenderThread( + () -> setNotification("Restart failed: " + e.getMessage(), true)); + } + }); + } + } + + private void setNotification(String message, boolean error) { + monitorNotification = message; + monitorNotificationError = error; + monitorNotificationExpiry = System.currentTimeMillis() + 5000; + } + + static List<String> parseCommandLine(String commandLine) { + List<String> tokens = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + boolean inQuotes = false; + boolean escaped = false; + + for (int i = 0; i < commandLine.length(); i++) { + char c = commandLine.charAt(i); + if (escaped) { + current.append(c); + escaped = false; + } else if (c == '\\') { + escaped = true; + } else if (c == '"') { + inQuotes = !inQuotes; + } else if (c == ' ' && !inQuotes) { + if (!current.isEmpty()) { + tokens.add(current.toString()); + current.setLength(0); + } + } else { + current.append(c); + } + } + if (!current.isEmpty()) { + tokens.add(current.toString()); + } + return tokens; + } + private void resetStats() { IntegrationInfo info = ctx.findSelectedIntegration(); if (info == null) { @@ -1845,6 +1982,9 @@ public class CamelMonitor extends CamelCommand { } } if (ctx.selectedPid != null) { + if (!ctx.infraTableFocused) { + hint(spans, "r", "restart"); + } hint(spans, "x", "stop"); hint(spans, "X", "kill"); } @@ -2779,6 +2919,7 @@ public class CamelMonitor extends CamelCommand { } } } + info.directory = runtime != null ? runtime.getString("directory") : null; info.javaVersion = runtime != null ? runtime.getString("javaVersion") : null; info.javaVendor = runtime != null ? runtime.getString("javaVendor") : null; info.javaVmName = runtime != null ? runtime.getString("javaVmName") : null; diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java index 099e7b2294ca..018fabc1a6f1 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java @@ -28,6 +28,7 @@ class IntegrationInfo { String javaVersion; String javaVendor; String javaVmName; + String directory; String profile; boolean devMode; String ready;
